/*
* Copyright (c) 2014 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.cdt.internal.qt.core.index;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent;
import org.eclipse.cdt.core.settings.model.ICDescriptionDelta;
import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
/**
* Represents a management of QMakeProjectInfo instances and manages life-cycle of all QMakeProjectInfo instances.
*/
public class QMakeProjectInfoManager {
private static final PDListener PD_LISTENER = new PDListener();
private static final RCListener RC_LISTENER = new RCListener();
// sync object for CACHE field
private static final Object CACHE_SYNC = new Object();
// a list of all QMakeProjectInfo instances
private static Map<IProject,QMakeProjectInfo> CACHE;
// called by QtPlugin activator to setup this class
public static final void start() {
synchronized (CACHE_SYNC) {
CACHE = new HashMap<IProject,QMakeProjectInfo>();
}
CoreModel.getDefault().addCProjectDescriptionListener(PD_LISTENER, CProjectDescriptionEvent.LOADED | CProjectDescriptionEvent.APPLIED);
ResourcesPlugin.getWorkspace().addResourceChangeListener(RC_LISTENER, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE);
}
// called by QtPlugin activator to clean up this class
public static final void stop() {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(RC_LISTENER);
CoreModel.getDefault().removeCProjectDescriptionListener(PD_LISTENER);
List<QMakeProjectInfo> infos;
synchronized (CACHE_SYNC) {
infos = new ArrayList<QMakeProjectInfo>(CACHE.values());
CACHE = null;
}
for (QMakeProjectInfo info : infos) {
if (info != null) {
info.destroy();
}
}
}
/**
* Returns a QMakeProjectInfo for an active project configuration of a specified project.
*
* @param project the project
* @return the QMakeProjectInfo; or null if the project does not have QtNature
*/
public static QMakeProjectInfo getQMakeProjectInfoFor(IProject project) {
return getQMakeProjectInfoFor(project, true);
}
private static QMakeProjectInfo getQMakeProjectInfoFor(IProject project, boolean create) {
QMakeProjectInfo info;
synchronized (CACHE_SYNC) {
// If the cache is null then this must be a late notification after shutdown. We
// can't do anything so don't try.
if (CACHE == null)
return null;
info = CACHE.get(project);
if (info != null) {
return info;
}
if (! create) {
// do not create, just return null
return null;
}
info = new QMakeProjectInfo(project);
CACHE.put(project, info);
}
info.updateState();
return info;
}
// removes the project from the CACHE
private static void removeProjectFromCache(IResource project) {
QMakeProjectInfo info;
synchronized (CACHE_SYNC) {
// If the cache is null then this must be a late notification after shutdown. We
// can't do anything so don't try.
if (CACHE == null)
return;
info = CACHE.remove(project);
}
if (info != null) {
info.destroy();
}
}
private static final class PDListener implements ICProjectDescriptionListener {
// called on active project configuration change
@Override
public void handleEvent(CProjectDescriptionEvent event) {
ICDescriptionDelta projectDelta = event.getProjectDelta();
if (projectDelta != null) {
if ((projectDelta.getChangeFlags() & ICDescriptionDelta.ACTIVE_CFG) != 0) {
QMakeProjectInfo info = getQMakeProjectInfoFor(event.getProject(), false);
if (info != null) {
info.updateState();
}
}
}
}
}
/**
* Listens on Eclipse file system changes.
*/
private static final class RCListener implements IResourceChangeListener {
@Override
public void resourceChanged(IResourceChangeEvent event) {
RDVisitor visitor = new RDVisitor();
// collect project to delete and changed files
switch (event.getType()) {
case IResourceChangeEvent.PRE_CLOSE:
case IResourceChangeEvent.PRE_DELETE:
IResource project = event.getResource();
if (project != null && project.getType() == IResource.PROJECT) {
visitor.addProjectToDelete(project);
}
break;
case IResourceChangeEvent.POST_CHANGE:
IResourceDelta delta = event.getDelta();
if (delta != null) {
try {
delta.accept(visitor);
} catch (CoreException e) {
// empty
}
}
break;
}
// process collected data
visitor.process();
}
}
private static final class RDVisitor implements IResourceDeltaVisitor {
private final Set<IResource> projectsToDelete = new HashSet<IResource>();
private final Set<IResource> projectsToUpdate = new HashSet<IResource>();
private final Set<IPath> changedFiles = new HashSet<IPath>();
@Override
public boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
if (resource != null) {
switch (resource.getType()) {
case IResource.FILE:
addChangedFile(resource);
return false;
case IResource.PROJECT:
switch (delta.getKind()) {
case IResourceDelta.CHANGED:
if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
addProjectToUpdate(resource);
}
return true;
case IResourceDelta.REMOVED:
addProjectToDelete(resource);
return false;
}
break;
}
}
return true;
}
private void addProjectToUpdate(IResource project) {
projectsToUpdate.add(project);
}
private void addProjectToDelete(IResource project) {
projectsToDelete.add(project);
}
private void addChangedFile(IResource file) {
IPath fullPath = file.getFullPath();
if (fullPath != null) {
changedFiles.add(fullPath);
}
}
public void process() {
// removing projects from CACHE
for(IResource project : projectsToDelete) {
removeProjectFromCache(project);
}
List<QMakeProjectInfo> infos;
synchronized (CACHE_SYNC) {
// If the cache is null then this must be a late notification after shutdown. We
// can't do anything so don't try.
if (CACHE == null)
return;
infos = new ArrayList<QMakeProjectInfo>(CACHE.values());
}
for (QMakeProjectInfo info : infos) {
// checking if any project description change or any of the changed files affect QMakeProjectInfo
if (projectsToUpdate.contains(info.getProject()) || info.containsAnySensitiveFile(changedFiles)) {
// if so then updating
info.updateState();
}
}
}
}
}