blob: 97ed5b0da7335fb0f60cc5ec986d2ed63e93dd79 [file] [log] [blame]
package com.intellij.openapi.roots.ui.configuration.projectRoot.daemon;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.MultiValuesMap;
import com.intellij.util.Alarm;
import com.intellij.util.EventDispatcher;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author nik
*/
public class ProjectStructureDaemonAnalyzer implements Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.ui.configuration.projectRoot.validation.ProjectStructureDaemonAnalyzer");
private final Map<ProjectStructureElement, ProjectStructureProblemsHolderImpl> myProblemHolders = new HashMap<ProjectStructureElement, ProjectStructureProblemsHolderImpl>();
private final MultiValuesMap<ProjectStructureElement, ProjectStructureElementUsage> mySourceElement2Usages = new MultiValuesMap<ProjectStructureElement, ProjectStructureElementUsage>();
private final MultiValuesMap<ProjectStructureElement, ProjectStructureElementUsage> myContainingElement2Usages = new MultiValuesMap<ProjectStructureElement, ProjectStructureElementUsage>();
private final Set<ProjectStructureElement> myElementWithNotCalculatedUsages = new HashSet<ProjectStructureElement>();
private final Set<ProjectStructureElement> myElementsToShowWarningIfUnused = new HashSet<ProjectStructureElement>();
private final Map<ProjectStructureElement, ProjectStructureProblemDescription> myWarningsAboutUnused = new HashMap<ProjectStructureElement, ProjectStructureProblemDescription>();
private final MergingUpdateQueue myAnalyzerQueue;
private final MergingUpdateQueue myResultsUpdateQueue;
private final EventDispatcher<ProjectStructureDaemonAnalyzerListener> myDispatcher = EventDispatcher.create(ProjectStructureDaemonAnalyzerListener.class);
private final AtomicBoolean myStopped = new AtomicBoolean(false);
private final ProjectConfigurationProblems myProjectConfigurationProblems;
public ProjectStructureDaemonAnalyzer(StructureConfigurableContext context) {
Disposer.register(context, this);
myProjectConfigurationProblems = new ProjectConfigurationProblems(this, context);
myAnalyzerQueue = new MergingUpdateQueue("Project Structure Daemon Analyzer", 300, false, null, this, null, Alarm.ThreadToUse.POOLED_THREAD);
myResultsUpdateQueue = new MergingUpdateQueue("Project Structure Analysis Results Updater", 300, false, MergingUpdateQueue.ANY_COMPONENT,
this, null, Alarm.ThreadToUse.SWING_THREAD);
}
private void doUpdate(final ProjectStructureElement element, final boolean check, final boolean collectUsages) {
if (myStopped.get()) return;
if (check) {
doCheck(element);
}
if (collectUsages) {
doCollectUsages(element);
}
}
private void doCheck(final ProjectStructureElement element) {
final ProjectStructureProblemsHolderImpl problemsHolder = new ProjectStructureProblemsHolderImpl();
new ReadAction() {
@Override
protected void run(final Result result) {
if (myStopped.get()) return;
if (LOG.isDebugEnabled()) {
LOG.debug("checking " + element);
}
ProjectStructureValidator.check(element, problemsHolder);
}
}.execute();
myResultsUpdateQueue.queue(new ProblemsComputedUpdate(element, problemsHolder));
}
private void doCollectUsages(final ProjectStructureElement element) {
final List<ProjectStructureElementUsage> usages = new ReadAction<List<ProjectStructureElementUsage>>() {
@Override
protected void run(final Result<List<ProjectStructureElementUsage>> result) {
if (myStopped.get()) return;
if (LOG.isDebugEnabled()) {
LOG.debug("collecting usages in " + element);
}
result.setResult(getUsagesInElement(element));
}
}.execute().getResultObject();
if (usages != null) {
myResultsUpdateQueue.queue(new UsagesCollectedUpdate(element, usages));
}
}
private static List<ProjectStructureElementUsage> getUsagesInElement(final ProjectStructureElement element) {
return ProjectStructureValidator.getUsagesInElement(element);
}
private void updateUsages(ProjectStructureElement element, List<ProjectStructureElementUsage> usages) {
removeUsagesInElement(element);
for (ProjectStructureElementUsage usage : usages) {
addUsage(usage);
}
myElementWithNotCalculatedUsages.remove(element);
myResultsUpdateQueue.queue(new ReportUnusedElementsUpdate());
}
public void queueUpdate(@NotNull final ProjectStructureElement element) {
queueUpdate(element, true, true);
}
private void queueUpdate(@NotNull final ProjectStructureElement element, final boolean check, final boolean collectUsages) {
if (LOG.isDebugEnabled()) {
LOG.debug("start " + (check ? "checking " : "") + (collectUsages ? "collecting usages " : "") + "for " + element);
}
if (collectUsages) {
myElementWithNotCalculatedUsages.add(element);
}
if (element.shouldShowWarningIfUnused()) {
myElementsToShowWarningIfUnused.add(element);
}
myAnalyzerQueue.queue(new AnalyzeElementUpdate(element, check, collectUsages));
}
public void removeElement(ProjectStructureElement element) {
removeElements(Collections.singletonList(element));
}
public void removeElements(@NotNull List<? extends ProjectStructureElement> elements) {
myElementWithNotCalculatedUsages.removeAll(elements);
myElementsToShowWarningIfUnused.removeAll(elements);
for (ProjectStructureElement element : elements) {
myWarningsAboutUnused.remove(element);
myProblemHolders.remove(element);
final Collection<ProjectStructureElementUsage> usages = mySourceElement2Usages.removeAll(element);
if (usages != null) {
for (ProjectStructureElementUsage usage : usages) {
myProblemHolders.remove(usage.getContainingElement());
}
}
removeUsagesInElement(element);
myDispatcher.getMulticaster().problemsChanged(element);
}
myResultsUpdateQueue.queue(new ReportUnusedElementsUpdate());
}
private void reportUnusedElements() {
if (!myElementWithNotCalculatedUsages.isEmpty()) return;
for (ProjectStructureElement element : myElementsToShowWarningIfUnused) {
final ProjectStructureProblemDescription warning;
final Collection<ProjectStructureElementUsage> usages = mySourceElement2Usages.get(element);
if (usages == null || usages.isEmpty()) {
warning = element.createUnusedElementWarning();
}
else {
warning = null;
}
final ProjectStructureProblemDescription old = myWarningsAboutUnused.put(element, warning);
ProjectStructureProblemsHolderImpl holder = myProblemHolders.get(element);
if (holder == null) {
holder = new ProjectStructureProblemsHolderImpl();
myProblemHolders.put(element, holder);
}
if (old != null) {
holder.removeProblem(old);
}
if (warning != null) {
holder.registerProblem(warning);
}
if (old != null || warning != null) {
myDispatcher.getMulticaster().problemsChanged(element);
}
}
}
private void removeUsagesInElement(ProjectStructureElement element) {
final Collection<ProjectStructureElementUsage> usages = myContainingElement2Usages.removeAll(element);
if (usages != null) {
for (ProjectStructureElementUsage usage : usages) {
mySourceElement2Usages.remove(usage.getSourceElement(), usage);
}
}
}
private void addUsage(@NotNull ProjectStructureElementUsage usage) {
mySourceElement2Usages.put(usage.getSourceElement(), usage);
myContainingElement2Usages.put(usage.getContainingElement(), usage);
}
public void stop() {
LOG.debug("analyzer stopped");
myStopped.set(true);
myAnalyzerQueue.cancelAllUpdates();
myResultsUpdateQueue.cancelAllUpdates();
clearCaches();
myAnalyzerQueue.deactivate();
myResultsUpdateQueue.deactivate();
}
public void clearCaches() {
LOG.debug("clear caches");
myProblemHolders.clear();
}
public void queueUpdateForAllElementsWithErrors() {
List<ProjectStructureElement> toUpdate = new ArrayList<ProjectStructureElement>();
for (Map.Entry<ProjectStructureElement, ProjectStructureProblemsHolderImpl> entry : myProblemHolders.entrySet()) {
if (entry.getValue().containsProblems()) {
toUpdate.add(entry.getKey());
}
}
myProblemHolders.clear();
LOG.debug("Adding to queue updates for " + toUpdate.size() + " problematic elements");
for (ProjectStructureElement element : toUpdate) {
queueUpdate(element);
}
}
@Override
public void dispose() {
myStopped.set(true);
myAnalyzerQueue.cancelAllUpdates();
myResultsUpdateQueue.cancelAllUpdates();
}
@Nullable
public ProjectStructureProblemsHolderImpl getProblemsHolder(ProjectStructureElement element) {
return myProblemHolders.get(element);
}
public Collection<ProjectStructureElementUsage> getUsages(ProjectStructureElement selected) {
ProjectStructureElement[] elements = myElementWithNotCalculatedUsages.toArray(new ProjectStructureElement[myElementWithNotCalculatedUsages.size()]);
for (ProjectStructureElement element : elements) {
updateUsages(element, getUsagesInElement(element));
}
final Collection<ProjectStructureElementUsage> usages = mySourceElement2Usages.get(selected);
return usages != null ? usages : Collections.<ProjectStructureElementUsage>emptyList();
}
public void addListener(ProjectStructureDaemonAnalyzerListener listener) {
LOG.debug("listener added " + listener);
myDispatcher.addListener(listener);
}
public void reset() {
LOG.debug("analyzer started");
myAnalyzerQueue.activate();
myResultsUpdateQueue.activate();
myAnalyzerQueue.queue(new Update("reset") {
@Override
public void run() {
myStopped.set(false);
}
});
}
public void clear() {
myWarningsAboutUnused.clear();
myElementsToShowWarningIfUnused.clear();
mySourceElement2Usages.clear();
myContainingElement2Usages.clear();
myElementWithNotCalculatedUsages.clear();
myProjectConfigurationProblems.clearProblems();
}
private class AnalyzeElementUpdate extends Update {
private final ProjectStructureElement myElement;
private final boolean myCheck;
private final boolean myCollectUsages;
private final Object[] myEqualityObjects;
public AnalyzeElementUpdate(ProjectStructureElement element, boolean check, boolean collectUsages) {
super(element);
myElement = element;
myCheck = check;
myCollectUsages = collectUsages;
myEqualityObjects = new Object[]{myElement, myCheck, myCollectUsages};
}
@Override
public boolean canEat(Update update) {
if (!(update instanceof AnalyzeElementUpdate)) return false;
final AnalyzeElementUpdate other = (AnalyzeElementUpdate)update;
return myElement.equals(other.myElement) && (!other.myCheck || myCheck) && (!other.myCollectUsages || myCollectUsages);
}
@NotNull
@Override
public Object[] getEqualityObjects() {
return myEqualityObjects;
}
@Override
public void run() {
try {
doUpdate(myElement, myCheck, myCollectUsages);
}
catch (Throwable t) {
LOG.error(t);
}
}
}
private class UsagesCollectedUpdate extends Update {
private final ProjectStructureElement myElement;
private final List<ProjectStructureElementUsage> myUsages;
private final Object[] myEqualityObjects;
public UsagesCollectedUpdate(ProjectStructureElement element, List<ProjectStructureElementUsage> usages) {
super(element);
myElement = element;
myUsages = usages;
myEqualityObjects = new Object[]{element, "usages collected"};
}
@NotNull
@Override
public Object[] getEqualityObjects() {
return myEqualityObjects;
}
@Override
public void run() {
if (myStopped.get()) return;
if (LOG.isDebugEnabled()) {
LOG.debug("updating usages for " + myElement);
}
updateUsages(myElement, myUsages);
}
}
private class ProblemsComputedUpdate extends Update {
private final ProjectStructureElement myElement;
private final ProjectStructureProblemsHolderImpl myProblemsHolder;
private final Object[] myEqualityObjects;
public ProblemsComputedUpdate(ProjectStructureElement element, ProjectStructureProblemsHolderImpl problemsHolder) {
super(element);
myElement = element;
myProblemsHolder = problemsHolder;
myEqualityObjects = new Object[]{element, "problems computed"};
}
@NotNull
@Override
public Object[] getEqualityObjects() {
return myEqualityObjects;
}
@Override
public void run() {
if (myStopped.get()) return;
if (LOG.isDebugEnabled()) {
LOG.debug("updating problems for " + myElement);
}
final ProjectStructureProblemDescription warning = myWarningsAboutUnused.get(myElement);
if (warning != null) {
myProblemsHolder.registerProblem(warning);
}
myProblemHolders.put(myElement, myProblemsHolder);
myDispatcher.getMulticaster().problemsChanged(myElement);
}
}
private class ReportUnusedElementsUpdate extends Update {
private ReportUnusedElementsUpdate() {
super("unused elements");
}
@Override
public void run() {
reportUnusedElements();
}
}
}