/******************************************************************************* * Copyright Technophobia Ltd 2012 * * This file is part of the Substeps Eclipse Plugin. * * The Substeps Eclipse Plugin is free software: you can redistribute it and/or modify * it under the terms of the Eclipse Public License v1.0. * * The Substeps Eclipse Plugin is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Eclipse Public License for more details. * * You should have received a copy of the Eclipse Public License * along with the Substeps Eclipse Plugin. If not, see <http://www.eclipse.org/legal/epl-v10.html>. ******************************************************************************/ package com.technophobia.eclipse.project.cache; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.core.IElementChangedListener; import org.eclipse.jdt.core.JavaCore; import com.technophobia.eclipse.project.ProjectChangedListener; import com.technophobia.eclipse.project.ProjectEventType; import com.technophobia.eclipse.project.ProjectFileChangedListener; import com.technophobia.eclipse.project.ProjectObserver; import com.technophobia.eclipse.project.cache.listener.ClassFileChangedListener; import com.technophobia.eclipse.project.cache.listener.ClasspathChangedListener; import com.technophobia.eclipse.project.cache.listener.FileWithExtensionChangedListener; import com.technophobia.eclipse.project.cache.listener.ProjectCreatedListener; import com.technophobia.eclipse.project.cache.listener.ProjectDeletedListener; import com.technophobia.substeps.FeatureEditorPlugin; import com.technophobia.substeps.observer.CacheMonitor; import com.technophobia.substeps.supplier.Callback1; public class CacheAwareProjectManager implements ProjectObserver { private final CacheMonitor<IProject>[] cacheMonitors; private final Collection<ProjectFileChangedListener> substepsChangeListeners; private final Collection<ProjectFileChangedListener> featureChangeListeners; private final Map<ProjectEventType, Set<ProjectChangedListener>> projectChangeListeners; private final Collection<IElementChangedListener> javaListeners; private final Collection<IResourceChangeListener> resourceChangeListeners; public CacheAwareProjectManager(final CacheMonitor<IProject>... cacheMonitors) { this.cacheMonitors = cacheMonitors; this.substepsChangeListeners = new HashSet<ProjectFileChangedListener>(); this.featureChangeListeners = new HashSet<ProjectFileChangedListener>(); this.projectChangeListeners = new HashMap<ProjectEventType, Set<ProjectChangedListener>>(); this.resourceChangeListeners = new HashSet<IResourceChangeListener>(); this.javaListeners = new ArrayList<IElementChangedListener>(); this.javaListeners.add(createClassFilesChangedListener()); this.javaListeners.add(createClasspathChangedListener()); this.javaListeners.add(createProjectCreatedListener()); this.javaListeners.add(createProjectDeletedListener()); this.javaListeners.add(createCheckSubstepsCompatabilityOnProjectCreationListener()); this.resourceChangeListeners.add(createSubstepsFileChangedListener()); } @Override public void registerFrameworkListeners() { for (final IElementChangedListener listener : javaListeners) { JavaCore.addElementChangedListener(listener); } for (final IResourceChangeListener resourceChangeListener : resourceChangeListeners) { ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceChangeListener, IResourceChangeEvent.POST_CHANGE); } } @Override public void unregisterFrameworkListeners() { for (final IElementChangedListener listener : javaListeners) { JavaCore.removeElementChangedListener(listener); } for (final IResourceChangeListener resourceChangeListener : resourceChangeListeners) { ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceChangeListener); } } @Override public void projectFileChange(final IProject project, final IFile file) { if (isSubstepsFile(file)) { FeatureEditorPlugin.instance().info( "Substeps file " + file.getLocation() + " has changed in project " + project); updateCaches(project); updateFileChangeListeners(file, substepsChangeListeners); } else if (isFeatureFile(file)) { FeatureEditorPlugin.instance().info( "Feature file " + file.getLocation() + " has changed in project " + project); updateCaches(project); updateFileChangeListeners(file, featureChangeListeners); } } @Override public void preferencesChanged(final IProject project) { FeatureEditorPlugin.instance().info("Preferences changed for project " + project); updateCaches(project); } @Override public void projectChanged(final IProject project) { updateCaches(project); for (final ProjectChangedListener listener : projectChangeListeners .get(ProjectEventType.ProjectConfigurationChanged)) { listener.projectChanged(project); } } @Override public void workspaceLoaded() { for (final IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { updateCaches(project); } } @Override public void addFeatureFileListener(final ProjectFileChangedListener listener) { this.featureChangeListeners.add(listener); } @Override public void removeFeatureFileListener(final ProjectFileChangedListener listener) { this.featureChangeListeners.remove(listener); } @Override public void addSubstepsFileListener(final ProjectFileChangedListener listener) { this.substepsChangeListeners.add(listener); } @Override public void removeSubstepsFileListener(final ProjectFileChangedListener listener) { this.substepsChangeListeners.remove(listener); } @Override public void addProjectListener(final ProjectEventType projectEventType, final ProjectChangedListener listener) { if (!projectChangeListeners.containsKey(projectEventType)) { projectChangeListeners.put(projectEventType, new HashSet<ProjectChangedListener>()); } this.projectChangeListeners.get(projectEventType).add(listener); } @Override public void removeProjectListener(final ProjectEventType projectEventType, final ProjectChangedListener listener) { if (projectChangeListeners.containsKey(projectEventType)) { projectChangeListeners.get(projectEventType).remove(listener); } } protected void updateCaches(final IProject project) { for (final CacheMonitor<IProject> cacheMonitor : cacheMonitors) { cacheMonitor.refreshCacheFor(project); } } protected void evictFromCaches(final IProject project) { for (final CacheMonitor<IProject> cacheMonitor : cacheMonitors) { cacheMonitor.evictFrom(project); } } protected IElementChangedListener createClassFilesChangedListener() { return new ClassFileChangedListener(updateProjectCachesCallback(ProjectEventType.SourceFileAnnotationsChanged)); } protected IElementChangedListener createClasspathChangedListener() { return new ClasspathChangedListener(updateProjectCachesCallback(ProjectEventType.ProjectDependenciesChanged)); } protected IElementChangedListener createProjectDeletedListener() { return new ProjectDeletedListener(removeProjectFromCacheCallback(ProjectEventType.ProjectRemoved)); } protected IElementChangedListener createProjectCreatedListener() { return new ProjectCreatedListener(updateProjectCachesCallback(ProjectEventType.ProjectInserted)); } private IResourceChangeListener createSubstepsFileChangedListener() { return new FileWithExtensionChangedListener(updateProjectCachesCallback(ProjectEventType.FeatureFileChanged), "feature", "substeps"); } private IElementChangedListener createCheckSubstepsCompatabilityOnProjectCreationListener() { return new ProjectCreatedListener(new Callback1<IProject>() { @Override public void doCallback(final IProject t) { FeatureEditorPlugin.instance().checkSubstepsCompatibilityFor(t); } }); } protected void notifyProjectChangeListeners(final IProject project, final ProjectEventType projectEventType) { if (projectChangeListeners.containsKey(projectEventType)) { for (final ProjectChangedListener listener : projectChangeListeners.get(projectEventType)) { listener.projectChanged(project); } } } private boolean isSubstepsFile(final IFile file) { return file.getFileExtension().toLowerCase().equals("substeps"); } private boolean isFeatureFile(final IFile file) { return file.getFileExtension().toLowerCase().equals("feature"); } private void updateFileChangeListeners(final IFile file, final Collection<ProjectFileChangedListener> listeners) { for (final ProjectFileChangedListener listener : listeners) { listener.projectFileChange(file.getProject(), file); } } private Callback1<IProject> updateProjectCachesCallback(final ProjectEventType projectEventType) { return new Callback1<IProject>() { @Override public void doCallback(final IProject project) { final Job job = new UpdateProjectInCachesJob("Update Substeps model for project " + project.getName(), project, projectEventType); job.setRule(ResourcesPlugin.getWorkspace().getRoot()); job.setPriority(Job.DECORATE); FeatureEditorPlugin.instance().info( "Found " + Job.getJobManager().find(project).length + " existing jobs"); Job.getJobManager().cancel(project); job.schedule(200); } }; } private Callback1<IProject> removeProjectFromCacheCallback(final ProjectEventType projectEventType) { return new Callback1<IProject>() { @Override public void doCallback(final IProject project) { final Job job = new Job("Update Substeps model for project " + project.getName()) { @Override protected IStatus run(final IProgressMonitor monitor) { evictFromCaches(project); notifyProjectChangeListeners(project, projectEventType); return Status.OK_STATUS; } }; job.setRule(ResourcesPlugin.getWorkspace().getRoot()); job.setPriority(Job.DECORATE); job.schedule(200); } }; } private final class UpdateProjectInCachesJob extends Job { private final IProject project; private final ProjectEventType projectEventType; private UpdateProjectInCachesJob(final String name, final IProject project, final ProjectEventType projectEventType) { super(name); this.project = project; this.projectEventType = projectEventType; } @Override protected IStatus run(final IProgressMonitor monitor) { notifyProjectChangeListeners(project, projectEventType); updateCaches(project); return Status.OK_STATUS; } @Override public boolean belongsTo(final Object family) { return project.equals(family); } } }