/**
* Aptana Studio
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
* Please see the license.html included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.editor.php.internal.builder;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFolder;
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.IResourceVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import com.aptana.core.logging.IdeLog;
import com.aptana.editor.php.PHPEditorPlugin;
import com.aptana.editor.php.core.PHPNature;
import com.aptana.editor.php.indexer.PHPGlobalIndexer;
import com.aptana.editor.php.internal.builder.preferences.DependenciesManager;
import com.aptana.editor.php.internal.builder.preferences.IProjectDependencyListener;
import com.aptana.editor.php.internal.builder.preferences.ProjectDependencies;
import com.aptana.editor.php.internal.core.builder.IBuildPath;
import com.aptana.editor.php.internal.core.builder.IBuildPathsListener;
import com.aptana.editor.php.internal.core.builder.IDirectory;
import com.aptana.editor.php.internal.core.builder.IModule;
/**
* BuildPathManager
*
* @author Denis Denisenko
*/
public final class BuildPathManager
{
private static Object mutex = new Object();
/**
* Build path manager instance.
*/
private static BuildPathManager instance = null;
/**
* Gets build path manager instance.
*
* @return build path manager instance.
*/
public static BuildPathManager getInstance()
{
synchronized (mutex)
{
if (instance == null)
{
instance = new BuildPathManager();
}
return instance;
}
}
/**
* Map of all existing build paths. Map from build path resource to build path.
*/
private Map<Object, IBuildPath> buildPaths = new HashMap<Object, IBuildPath>();
/**
* Listeners.
*/
private Set<IBuildPathsListener> listeners = new HashSet<IBuildPathsListener>();
/**
* Gets all existing build paths.
*
* @return existing build paths.
*/
public synchronized List<IBuildPath> getBuildPaths()
{
List<IBuildPath> result = new ArrayList<IBuildPath>();
result.addAll(buildPaths.values());
return result;
}
/**
* Gets build path by resource, build path was originally created from.
*
* @param resource
* - resource.
* @return build path or null.
*/
public synchronized IBuildPath getBuildPathByResource(Object resource)
{
return buildPaths.get(resource);
}
/**
* Gets module by resource, module was originally created from.
*
* @param resource
* - resource.
* @return module or null.
*/
public synchronized IModule getModuleByResource(Object resource)
{
if (resource == null)
{
return null;
}
for (IBuildPath path : buildPaths.values())
{
// double check the path - See http://jira.appcelerator.org/browse/APSTUD-3048
if (path != null)
{
IModule module = path.getModule(resource);
if (module != null)
{
return module;
}
}
}
return null;
}
/**
* Gets directory by resource, directory was originally created from.
*
* @param resource
* - resource.
* @return module or null.
*/
public synchronized IDirectory getDirectoryByResource(Object resource)
{
for (IBuildPath path : buildPaths.values())
{
IDirectory directory = path.getDirectory(resource);
if (directory != null)
{
return directory;
}
}
return null;
}
public synchronized void addBuildPathChangeListener(IBuildPathsListener listener)
{
listeners.add(listener);
}
public synchronized void removeBuildPathChangeListener(IBuildPathsListener listener)
{
listeners.remove(listener);
}
/**
* Adds build path.
*
* @param resource
* - build path resource.
* @return added build path or null if build path cannot be created
*/
public synchronized IBuildPath addBuildPath(Object resource)
{
IBuildPath path = createBuildPathByResource(resource);
internalAddBuldPath(resource, path);
return path;
}
private void internalAddBuldPath(Object resource, IBuildPath path)
{
buildPaths.put(resource, path);
List<IBuildPath> added = new ArrayList<IBuildPath>(1);
List<IBuildPath> removed = new ArrayList<IBuildPath>(0);
added.add(path);
notifyChanged(added, removed);
}
/**
* Removes build path.
*
* @param resource
* - build path resource.
*/
public synchronized void removeBuildPath(Object resource)
{
IBuildPath path = getBuildPathByResource(resource);
if (path == null)
{
return;
}
buildPaths.remove(resource);
List<IBuildPath> added = new ArrayList<IBuildPath>(0);
List<IBuildPath> removed = new ArrayList<IBuildPath>(1);
removed.add(path);
notifyChanged(added, removed);
}
/**
* BuildPathManager private constructor.
*/
private BuildPathManager()
{
long l0 = System.currentTimeMillis();
IWorkspace workspace = ResourcesPlugin.getWorkspace();
indexExternalLibraries();
indexLocalProjects(workspace);
bindListeners(workspace);
IdeLog.logInfo(PHPEditorPlugin.getDefault(),
"Indexer init: " + (System.currentTimeMillis() - l0) + "ms)", null, //$NON-NLS-1$ //$NON-NLS-2$
PHPEditorPlugin.DEBUG_SCOPE);
}
public void indexExternalLibraries()
{
IPHPLibrary[] all = LibraryManager.getInstance().getAllLibraries();
for (IPHPLibrary l : all)
{
for (String s : l.getDirectories())
{
File path = new File(s);
if (path != null)
{
FileSystemBuildPath fileSystemBuildPath = new FileSystemBuildPath(path);
internalAddBuldPath(path, fileSystemBuildPath);
}
}
}
LibraryManager.getInstance().addLibraryListener(new ILibraryListener()
{
public void librariesChanged(Set<IPHPLibrary> turnedOn, Set<IPHPLibrary> turnedOf)
{
try
{
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
root.accept(new IResourceVisitor()
{
public boolean visit(IResource resource)
{
// ignoring inaccessible resources
if (!resource.isAccessible())
{
return false;
}
if (resource instanceof IProject)
{
if (buildPaths.containsKey(resource))
{
IProject project = (IProject) resource;
ProjectDependencies dependencies = DependenciesManager.getDependencies(project);
handleDependenciesChange(project, dependencies);
}
return false;
}
return true;
}
});
}
catch (CoreException e)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), "Error handling a libraries change", e); //$NON-NLS-1$
}
}
public void userLibrariesChanged(UserLibrary[] newLibraries)
{
Set<IPHPLibrary> emptySet = Collections.emptySet();
Set<File> files = new HashSet<File>();
for (Object o : buildPaths.keySet())
{
if (o instanceof File)
{
files.add((File) o);
}
}
Set<File> currrentFiles = new HashSet<File>(files);
IPHPLibrary[] current = LibraryManager.getInstance().getAllLibraries();
for (IPHPLibrary l : current)
{
List<String> directories = l.getDirectories();
for (String s : directories)
{
File file = new File(s);
files.remove(file);
}
}
for (File f : files)
{
IBuildPath path = getBuildPathByResource(f);
removeBuildPath(f);
path.close(); // $codepro.audit.disable closeInFinally
currrentFiles.remove(f);
}
for (UserLibrary l : newLibraries)
{
if (!LibraryManager.getInstance().isTurnedOn(l))
{
continue;
}
List<String> directories = l.getDirectories();
for (String s : directories)
{
File file = new File(s);
if (!buildPaths.containsKey(file))
{
FileSystemBuildPath fileSystemBuildPath = new FileSystemBuildPath(file);
internalAddBuldPath(file, fileSystemBuildPath);
}
}
}
librariesChanged(emptySet, emptySet);
}
});
}
/**
* Binds required listeners.
*
* @param workspace
* - workspace.
*/
private void bindListeners(IWorkspace workspace)
{
// binding listener to workspace
IResourceChangeListener workspaceListener = new IResourceChangeListener()
{
/**
* {@inheritDoc}
*/
public void resourceChanged(IResourceChangeEvent event)
{
final Set<IProject> added = new HashSet<IProject>();
final Set<IProject> removed = new HashSet<IProject>();
IResourceDelta delta = event.getDelta();
if (delta != null)
{
IResourceDelta[] affectedChildren = event.getDelta().getAffectedChildren();
for (IResourceDelta resourceDelta : affectedChildren)
{
IResource resource = resourceDelta.getResource();
if (resource instanceof IProject)
{
IProject project = resource.getProject();
try
{
switch (resourceDelta.getKind())
{
case IResourceDelta.ADDED:
if (project.isAccessible())
{
addProject(project, added);
}
break;
case IResourceDelta.REMOVED:
removeProject(project, removed);
break;
case IResourceDelta.CHANGED:
// This will deal with the open (and potential close)
if (project.isAccessible())
{
addProject(project, added);
}
else
{
removeProject(project, removed);
}
break;
}
}
catch (CoreException e)
{
IdeLog.logError(PHPEditorPlugin.getDefault(),
"Error handling a resource-change event", e); //$NON-NLS-1$
}
}
}
}
else
{
// Deal with closing project
if (event.getResource() instanceof IProject && event.getType() == IResourceChangeEvent.PRE_CLOSE)
{
removed.add((IProject) event.getResource());
}
}
if (!added.isEmpty() || !removed.isEmpty())
{
handleChanged(added, removed);
}
}
private void addProject(IProject project, Set<IProject> toAdd) throws CoreException
{
if (project.hasNature(PHPNature.NATURE_ID) && !buildPaths.containsKey(project))
{
toAdd.add(project);
}
}
private void removeProject(IProject project, Set<IProject> toRemove)
{
if (buildPaths.containsKey(project))
{
toRemove.add(project);
}
}
};
workspace.addResourceChangeListener(workspaceListener);
// binding listener to project dependencies changes
DependenciesManager.addListener(new IProjectDependencyListener()
{
/**
* {@inheritDoc}
*/
public void dependenciesChanged(IProject project, ProjectDependencies dependencies)
{
// ignoring inaccessible projects
if (!project.isAccessible())
{
return;
}
handleDependenciesChange(project, dependencies);
}
});
}
/**
* Handles changes in the projects state.
*
* @param added
* - added projects.
* @param removed
* - remove projects.
*/
public void handleChanged(Set<IProject> added, Set<IProject> removed)
{
List<IBuildPath> addedPaths = new ArrayList<IBuildPath>();
List<IBuildPath> removedPaths = new ArrayList<IBuildPath>();
if (added.isEmpty() && removed.isEmpty())
{
return;
}
List<IProject> toRemove = new ArrayList<IProject>();
for (IProject addedProject : added)
{
IBuildPath path = createBuildPathByResource(addedProject);
if (path != null)
{
buildPaths.put(addedProject, path);
addedPaths.add(path);
}
}
for (IProject removedProject : removed)
{
IBuildPath path = getBuildPathByResource(removedProject);
if (path != null)
{
removedPaths.add(path);
toRemove.add(removedProject);
}
}
for (IProject toRemoveProject : toRemove)
{
IBuildPath path = buildPaths.get(toRemoveProject);
buildPaths.remove(toRemoveProject);
removeDepencies(path);
}
for (IBuildPath path : removedPaths)
{
path.close(); // $codepro.audit.disable closeInFinally
}
notifyChanged(addedPaths, removedPaths);
if (!added.isEmpty())
{
updateProjectsDependencies(ResourcesPlugin.getWorkspace().getRoot());
}
PHPGlobalIndexer.getInstance().indexLocalModules();
}
/**
* Notifies build paths changed.
*
* @param added
* - added build paths.
* @param removed
* - removed build paths.
*/
private void notifyChanged(List<IBuildPath> added, List<IBuildPath> removed)
{
for (IBuildPathsListener listener : listeners)
{
try
{
listener.changed(added, removed);
}
catch (Throwable th)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), "Unable notifying build path change listener", th); //$NON-NLS-1$
}
}
}
private void indexLocalProjects(IWorkspace workspace)
{
// creating build path for each project
IWorkspaceRoot root = workspace.getRoot();
try
{
root.accept(new IResourceVisitor()
{
public boolean visit(IResource resource)
{
// not visiting inaccessible resources
if (!resource.isAccessible())
{
return false;
}
if (resource instanceof IProject)
{
if (!buildPaths.containsKey(resource))
{
IBuildPath path = createBuildPathByResource(resource);
if (path != null)
{
buildPaths.put(resource, path);
}
}
return false;
}
return true;
}
});
}
catch (CoreException e)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), "Error indexing local projects", e); //$NON-NLS-1$
}
updateProjectsDependencies(root);
}
/**
* Traverse the accessible projects in the workspace and update their dependencies. This operation is needed on
* startup and when a project is opened.
*
* @param root
* The workspace root to visit and update
*/
private void updateProjectsDependencies(IWorkspaceRoot root)
{
try
{
root.accept(new IResourceVisitor()
{
public boolean visit(IResource resource)
{
// ignoring inaccessible resources
if (!resource.isAccessible())
{
return false;
}
if (resource instanceof IProject)
{
if (buildPaths.containsKey(resource))
{
IProject project = (IProject) resource;
ProjectDependencies dependencies = DependenciesManager.getDependencies(project);
handleDependenciesChange(project, dependencies);
}
return false;
}
return true;
}
});
}
catch (CoreException e)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), "Error updating the project dependencies", e); //$NON-NLS-1$
}
}
/**
* Creates new build path instance by resource.
*
* @param resource
* - resource to create build path from.
* @return new build path or null if resource is not recognized.
*/
private IBuildPath createBuildPathByResource(Object resource)
{
// ignoring inaccessible resources
if (resource instanceof IResource)
{
if (!((IResource) resource).isAccessible())
{
return null;
}
if (resource instanceof IProject)
{
IProject project = (IProject) resource;
try
{
if (((IProject) resource).hasNature(PHPNature.NATURE_ID))
{
return new ProjectBuildPath(project);
}
}
catch (CoreException e)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), "Error creating a build-path", e); //$NON-NLS-1$
}
}
}
else if (resource instanceof File && ((File) resource).exists() && ((File) resource).isDirectory())
{
return new FileSystemBuildPath((File) resource);
}
return null;
}
/**
* Handles project dependencies change.
*
* @param project
* - project.
* @param dependencies
* - dependencies.
*/
private void handleDependenciesChange(IProject project, ProjectDependencies dependencies)
{
IBuildPath path = buildPaths.get(project);
if (path == null)
{
return;
}
path.clearDependencies();
for (IResource projectDependency : dependencies.getWorkspaceResources())
{
if (projectDependency instanceof IProject)
{
IBuildPath dependencyBuildPath = buildPaths.get(projectDependency);
if (dependencyBuildPath != null)
{
path.addDependency(dependencyBuildPath);
}
}
else if (projectDependency instanceof IFolder)
{
IBuildPath dependencyBuildPath = new WorkspaceFolderBuildpath((IFolder) projectDependency);
path.addDependency(dependencyBuildPath);
}
}
for (File directoryDependency : dependencies.getDirectories())
{
// IBuildPath dependencyBuildPath =
// new FileSystemBuildPath(directoryDependency);
IBuildPath dependencyBuildPath = addBuildPath(directoryDependency);
// buildPaths.put(directoryDependency, dependencyBuildPath);
path.addDependency(dependencyBuildPath);
}
IPHPLibrary[] allLibraries = LibraryManager.getInstance().getAllLibraries();
Set<IPHPLibrary> usedLibraries = new HashSet<IPHPLibrary>(Arrays.asList(allLibraries));
if (dependencies.isUsesCustomLibs())
{
List<String> notUsedLibrariesIds = dependencies.getNotUsedLibrariesIds();
for (String s : notUsedLibrariesIds)
{
IPHPLibrary library = LibraryManager.getInstance().getLibrary(s);
if (library != null)
{
usedLibraries.remove(library);
}
}
}
else
{
for (IPHPLibrary l : allLibraries)
{
if (!l.isTurnedOn())
{
usedLibraries.remove(l);
}
}
}
for (IPHPLibrary l : usedLibraries)
{
for (String s : l.getDirectories())
{
File fl = new File(s);
IBuildPath dependency = buildPaths.get(fl);
if (dependency != null)
{
path.addDependency(dependency);
}
}
}
}
/**
* Removes this build path from all who depends on it.
*
* @param path
* - path.
*/
private void removeDepencies(IBuildPath path)
{
for (IBuildPath currentPath : buildPaths.values())
{
currentPath.removeDependency(path);
}
}
}