blob: 2dfa17da3ab88238e5eb63c47567a399198f7e6f [file] [log] [blame]
/*
* 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 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.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 = annotator.collectInformation(file);
if (info != null) {
Result annotationResult = annotator.doAnnotate(info);
if (annotationResult == null) {
return ProblemDescriptor.EMPTY_ARRAY;
}
AnnotationHolderImpl annotationHolder = new AnnotationHolderImpl(new AnnotationSession(file));
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()) {
continue;
}
final PsiElement startElement = file.findElementAt(annotation.getStartOffset());
final PsiElement endElement = file.findElementAt(annotation.getEndOffset() - 1);
if (startElement == null || endElement == null) {
continue;
}
LocalQuickFix[] quickFixes = toLocalQuickFixes(annotation.getQuickFixes(), quickFixMappingCache);
ProblemDescriptor descriptor = manager.createProblemDescriptor(startElement,
endElement,
annotation.getMessage(),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
false,
quickFixes);
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
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();
}
}
}