blob: 9acad44ae743c42e1a69dfa4e81a3e7b64d9f167 [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.util.xml.highlighting;
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.InspectionProfile;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ex.InspectionToolWrapper;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.profile.Profile;
import com.intellij.profile.ProfileChangeAdapter;
import com.intellij.profile.codeInspection.InspectionProfileManager;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.EventDispatcher;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xml.DomElement;
import com.intellij.util.xml.DomFileElement;
import com.intellij.util.xml.DomUtil;
import com.intellij.util.xml.impl.DomApplicationComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
public class DomElementAnnotationsManagerImpl extends DomElementAnnotationsManager {
public static final Object LOCK = new Object();
private static final Key<DomElementsProblemsHolderImpl> DOM_PROBLEM_HOLDER_KEY = Key.create("DomProblemHolder");
private static final Key<CachedValue<Boolean>> CACHED_VALUE_KEY = Key.create("DomProblemHolderCachedValue");
private final EventDispatcher<DomHighlightingListener> myDispatcher = EventDispatcher.create(DomHighlightingListener.class);
private static final DomElementsProblemsHolder EMPTY_PROBLEMS_HOLDER = new DomElementsProblemsHolder() {
@Override
@NotNull
public List<DomElementProblemDescriptor> getProblems(DomElement domElement) {
return Collections.emptyList();
}
@Override
public List<DomElementProblemDescriptor> getProblems(final DomElement domElement, boolean includeXmlProblems) {
return Collections.emptyList();
}
@Override
public List<DomElementProblemDescriptor> getProblems(final DomElement domElement,
final boolean includeXmlProblems,
final boolean withChildren) {
return Collections.emptyList();
}
@Override
public List<DomElementProblemDescriptor> getProblems(DomElement domElement,
final boolean includeXmlProblems,
final boolean withChildren,
HighlightSeverity minSeverity) {
return Collections.emptyList();
}
@Override
public List<DomElementProblemDescriptor> getProblems(DomElement domElement, final boolean withChildren, HighlightSeverity minSeverity) {
return Collections.emptyList();
}
@Override
public List<DomElementProblemDescriptor> getAllProblems() {
return Collections.emptyList();
}
@Override
public List<DomElementProblemDescriptor> getAllProblems(@NotNull DomElementsInspection inspection) {
return Collections.emptyList();
}
@Override
public boolean isInspectionCompleted(@NotNull final DomElementsInspection inspectionClass) {
return false;
}
};
private final Project myProject;
public DomElementAnnotationsManagerImpl(Project project) {
myProject = project;
final ProfileChangeAdapter profileChangeAdapter = new ProfileChangeAdapter() {
@Override
public void profileActivated(Profile oldProfile, Profile profile) {
dropAnnotationsCache();
}
@Override
public void profileChanged(Profile profile) {
dropAnnotationsCache();
}
};
final InspectionProfileManager inspectionProfileManager = InspectionProfileManager.getInstance();
inspectionProfileManager.addProfileChangeListener(profileChangeAdapter, project);
Disposer.register(project, new Disposable() {
@Override
public void dispose() {
inspectionProfileManager.removeProfileChangeListener(profileChangeAdapter);
}
});
}
@Override
public void dropAnnotationsCache() {
incModificationCount();
}
public final List<DomElementProblemDescriptor> appendProblems(@NotNull DomFileElement element, @NotNull DomElementAnnotationHolder annotationHolder, Class<? extends DomElementsInspection> inspectionClass) {
final DomElementAnnotationHolderImpl holderImpl = (DomElementAnnotationHolderImpl)annotationHolder;
synchronized (LOCK) {
final DomElementsProblemsHolderImpl holder = _getOrCreateProblemsHolder(element);
holder.appendProblems(holderImpl, inspectionClass);
}
myDispatcher.getMulticaster().highlightingFinished(element);
return Collections.unmodifiableList(holderImpl);
}
private DomElementsProblemsHolderImpl _getOrCreateProblemsHolder(final DomFileElement element) {
DomElementsProblemsHolderImpl holder;
final DomElement rootElement = element.getRootElement();
final XmlTag rootTag = rootElement.getXmlTag();
if (rootTag == null) return new DomElementsProblemsHolderImpl(element);
holder = rootTag.getUserData(DOM_PROBLEM_HOLDER_KEY);
if (isHolderOutdated(element.getFile()) || holder == null) {
holder = new DomElementsProblemsHolderImpl(element);
rootTag.putUserData(DOM_PROBLEM_HOLDER_KEY, holder);
final CachedValue<Boolean> cachedValue = CachedValuesManager.getManager(myProject).createCachedValue(new CachedValueProvider<Boolean>() {
@Override
public Result<Boolean> compute() {
return new Result<Boolean>(Boolean.FALSE, element, PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT, DomElementAnnotationsManagerImpl.this, ProjectRootManager.getInstance(myProject));
}
}, false);
cachedValue.getValue();
element.getFile().putUserData(CACHED_VALUE_KEY, cachedValue);
}
return holder;
}
public static boolean isHolderUpToDate(DomElement element) {
synchronized (LOCK) {
return !isHolderOutdated(DomUtil.getFile(element));
}
}
public static void outdateProblemHolder(final DomElement element) {
synchronized (LOCK) {
DomUtil.getFile(element).putUserData(CACHED_VALUE_KEY, null);
}
}
private static boolean isHolderOutdated(final XmlFile file) {
final CachedValue<Boolean> cachedValue = file.getUserData(CACHED_VALUE_KEY);
return cachedValue == null || !cachedValue.hasUpToDateValue();
}
@Override
@NotNull
public DomElementsProblemsHolder getProblemHolder(DomElement element) {
if (element == null || !element.isValid()) return EMPTY_PROBLEMS_HOLDER;
final DomFileElement<DomElement> fileElement = DomUtil.getFileElement(element);
synchronized (LOCK) {
final XmlTag tag = fileElement.getRootElement().getXmlTag();
if (tag != null) {
final DomElementsProblemsHolder readyHolder = tag.getUserData(DOM_PROBLEM_HOLDER_KEY);
if (readyHolder != null) {
return readyHolder;
}
}
return EMPTY_PROBLEMS_HOLDER;
}
}
@Override
@NotNull
public DomElementsProblemsHolder getCachedProblemHolder(DomElement element) {
return getProblemHolder(element);
}
public static void annotate(final DomElement element, final DomElementAnnotationHolder holder, final Class rootClass) {
final DomElementsAnnotator annotator = DomApplicationComponent.getInstance().getAnnotator(rootClass);
if (annotator != null) {
annotator.annotate(element, holder);
}
}
@Override
public List<ProblemDescriptor> createProblemDescriptors(final InspectionManager manager, DomElementProblemDescriptor problemDescriptor) {
return ContainerUtil.createMaybeSingletonList(DomElementsHighlightingUtil.createProblemDescriptors(manager, problemDescriptor));
}
@Override
public boolean isHighlightingFinished(final DomElement[] domElements) {
for (final DomElement domElement : domElements) {
if (getHighlightStatus(domElement) != DomHighlightStatus.INSPECTIONS_FINISHED) {
return false;
}
}
return true;
}
@Override
public void addHighlightingListener(DomHighlightingListener listener, Disposable parentDisposable) {
myDispatcher.addListener(listener, parentDisposable);
}
@Override
public DomHighlightingHelper getHighlightingHelper() {
return DomHighlightingHelperImpl.INSTANCE;
}
@Override
@NotNull
public <T extends DomElement> List<DomElementProblemDescriptor> checkFileElement(@NotNull final DomFileElement<T> domFileElement,
@NotNull final DomElementsInspection<T> inspection,
boolean onTheFly) {
final DomElementsProblemsHolder problemHolder = getProblemHolder(domFileElement);
if (isHolderUpToDate(domFileElement) && problemHolder.isInspectionCompleted(inspection)) {
return problemHolder.getAllProblems(inspection);
}
final DomElementAnnotationHolder holder = new DomElementAnnotationHolderImpl(onTheFly);
inspection.checkFileElement(domFileElement, holder);
return appendProblems(domFileElement, holder, inspection.getClass());
}
public List<DomElementsInspection> getSuitableDomInspections(final DomFileElement fileElement, boolean enabledOnly) {
Class rootType = fileElement.getRootElementClass();
final InspectionProfile profile = getInspectionProfile(fileElement);
final List<DomElementsInspection> inspections = new SmartList<DomElementsInspection>();
for (final InspectionToolWrapper toolWrapper : profile.getInspectionTools(fileElement.getFile())) {
if (!enabledOnly || profile.isToolEnabled(HighlightDisplayKey.find(toolWrapper.getShortName()), fileElement.getFile())) {
ContainerUtil.addIfNotNull(getSuitableInspection(toolWrapper.getTool(), rootType), inspections);
}
}
return inspections;
}
protected InspectionProfile getInspectionProfile(final DomFileElement fileElement) {
return InspectionProjectProfileManager.getInstance(fileElement.getManager().getProject()).getInspectionProfile();
}
@Nullable
private static DomElementsInspection getSuitableInspection(InspectionProfileEntry entry, Class rootType) {
if (entry instanceof DomElementsInspection) {
if (((DomElementsInspection)entry).getDomClasses().contains(rootType)) {
return (DomElementsInspection) entry;
}
}
return null;
}
@Nullable public <T extends DomElement> DomElementsInspection<T> getMockInspection(DomFileElement<T> root) {
if (root.getFileDescription().isAutomaticHighlightingEnabled()) {
return new MockAnnotatingDomInspection<T>(root.getRootElementClass());
}
if (getSuitableDomInspections(root, false).isEmpty()) {
return new MockDomInspection<T>(root.getRootElementClass());
}
return null;
}
private static boolean areInspectionsFinished(DomElementsProblemsHolderImpl holder, final List<DomElementsInspection> suitableInspections) {
for (final DomElementsInspection inspection : suitableInspections) {
if (!holder.isInspectionCompleted(inspection)) {
return false;
}
}
return true;
}
@NotNull
public DomHighlightStatus getHighlightStatus(final DomElement element) {
synchronized (LOCK) {
final DomFileElement<DomElement> root = DomUtil.getFileElement(element);
if (!isHolderOutdated(root.getFile())) {
final DomElementsProblemsHolder holder = getProblemHolder(element);
if (holder instanceof DomElementsProblemsHolderImpl) {
DomElementsProblemsHolderImpl holderImpl = (DomElementsProblemsHolderImpl)holder;
final List<DomElementsInspection> suitableInspections = getSuitableDomInspections(root, true);
final DomElementsInspection mockInspection = getMockInspection(root);
final boolean annotatorsFinished = mockInspection == null || holderImpl.isInspectionCompleted(mockInspection);
final boolean inspectionsFinished = areInspectionsFinished(holderImpl, suitableInspections);
if (annotatorsFinished) {
if (suitableInspections.isEmpty() || inspectionsFinished) return DomHighlightStatus.INSPECTIONS_FINISHED;
return DomHighlightStatus.ANNOTATORS_FINISHED;
}
}
}
return DomHighlightStatus.NONE;
}
}
}