/*******************************************************************************
* Copyright (c) 2009 IBM Corporation 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
*
* Contributors:
* IBM Corporation - initial API and implementation
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.debug.core.pathmapper;
import java.io.File;
import java.io.FileFilter;
import java.util.*;
import java.util.regex.Pattern;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager.ContentTypeChangeEvent;
import org.eclipse.core.runtime.content.IContentTypeManager.IContentTypeChangeListener;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.dltk.core.*;
import org.eclipse.dltk.core.environment.EnvironmentManager;
import org.eclipse.dltk.core.environment.EnvironmentPathUtils;
import org.eclipse.dltk.core.environment.IEnvironment;
import org.eclipse.php.internal.core.PHPCorePlugin;
import org.eclipse.php.internal.core.documentModel.provisional.contenttype.ContentTypeIdForPHP;
import org.eclipse.php.internal.core.includepath.IncludePath;
import org.eclipse.php.internal.core.util.PHPSearchEngine;
import org.eclipse.php.internal.core.util.PHPSearchEngine.ExternalFileResult;
import org.eclipse.php.internal.core.util.PHPSearchEngine.IncludedFileResult;
import org.eclipse.php.internal.core.util.PHPSearchEngine.ResourceResult;
import org.eclipse.php.internal.core.util.PHPSearchEngine.Result;
import org.eclipse.php.internal.debug.core.IPHPDebugConstants;
import org.eclipse.php.internal.debug.core.PHPDebugPlugin;
import org.eclipse.php.internal.debug.core.pathmapper.PathEntry.Type;
import org.eclipse.php.internal.debug.core.pathmapper.PathMapper.Mapping.MappingSource;
import org.eclipse.php.internal.debug.core.zend.model.PHPDebugTarget;
import org.eclipse.ui.*;
import org.eclipse.ui.ide.FileStoreEditorInput;
public class DebugSearchEngine {
private static PHPFilenameFilter PHP_FILTER = new PHPFilenameFilter();
private static IPathEntryFilter[] filters;
/**
* Searches for all local resources that match provided remote file, and
* returns it in best match order. This method skips internal PHP search
* mechanism, going straight to the path mapper, so it's good only for
* resolving absolute paths.
*
* @param remoteFile
* Path of the file on server. This argument must not be
* <code>null</code>.
* @param debugTarget
* Current debug target
* @return path entry or <code>null</code> in case it could not be found
*/
public static PathEntry find(String remoteFile, IDebugTarget debugTarget) {
return find(remoteFile, debugTarget, null, null);
}
/**
* Searches for all local resources that match provided remote file, and
* returns it in best match order.
*
* @param remoteFile
* Path of the file on server. This argument must not be
* <code>null</code>.
* @param debugTarget
* Current debug target
* @param currentWorkingDir
* Current working directory of PHP process
* @param currentScriptDir
* Directory of current PHP file
* @return path entry or <code>null</code> in case it could not be found
*/
public static PathEntry find(String remoteFile, IDebugTarget debugTarget, String currentWorkingDir,
String currentScriptDir) {
if (remoteFile == null) {
throw new NullPointerException();
}
PathEntry pathEntry = null;
ILaunchConfiguration launchConfiguration = debugTarget.getLaunch().getLaunchConfiguration();
IProject project = null;
if (currentScriptDir != null) {
IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(currentScriptDir);
if (resource != null) {
project = resource.getProject();
}
}
if (project == null && debugTarget instanceof PHPDebugTarget) {
project = ((PHPDebugTarget) debugTarget).getProject();
}
if (project == null) {
String projectName;
try {
projectName = launchConfiguration.getAttribute(IPHPDebugConstants.PHP_Project, (String) null);
if (projectName != null) {
project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
}
} catch (CoreException e) {
PHPDebugPlugin.log(e);
}
}
// If the given path is not absolute - use internal PHP search
// mechanism:
if (!VirtualPath.isAbsolute(remoteFile)) {
if (project != null && currentWorkingDir != null && currentScriptDir != null) {
// This is not a full path, search using PHP Search Engine:
Result<?, ?> result = PHPSearchEngine.find(remoteFile, currentWorkingDir, currentScriptDir, project);
if (result instanceof ExternalFileResult) {
ExternalFileResult extFileResult = (ExternalFileResult) result;
return new PathEntry(extFileResult.getFile().getAbsolutePath(), Type.EXTERNAL,
extFileResult.getContainer());
}
if (result instanceof IncludedFileResult) {
IncludedFileResult incFileResult = (IncludedFileResult) result;
IBuildpathEntry container = incFileResult.getContainer();
Type type = (container.getEntryKind() == IBuildpathEntry.BPE_VARIABLE) ? Type.INCLUDE_VAR
: Type.INCLUDE_FOLDER;
return new PathEntry(incFileResult.getFile().getAbsolutePath(), type, container);
}
if (result != null) {
// workspace file
ResourceResult resResult = (ResourceResult) result;
IResource resource = resResult.getFile();
return new PathEntry(resource.getFullPath().toString(), Type.WORKSPACE, resource.getParent());
}
}
return null;
}
PathMapper pathMapper = PathMapperRegistry.getByLaunchConfiguration(launchConfiguration);
if (pathMapper != null) {
pathEntry = find(pathMapper, remoteFile, project, debugTarget);
}
return pathEntry;
}
/**
* Searches for all local resources that match provided remote file, and
* returns it in best match order.
*
* @param pathMapper
* Path mapper to look at
* @param remoteFile
* Path of the file on server. This argument must not be
* <code>null</code>.
* @param debugTarget
* Current debug target
* @return path entry or <code>null</code> in case it could not be found
*/
public static PathEntry find(final PathMapper pathMapper, final String remoteFile, final IProject currentProject,
final IDebugTarget debugTarget) {
final PathEntry[] localFile = new PathEntry[1];
Job findJob = new Job(Messages.DebugSearchEngine_0) {
protected IStatus run(IProgressMonitor monitor) {
// First, look into the path mapper:
localFile[0] = pathMapper.getLocalFile(remoteFile);
if (localFile[0] != null) {
if (localFile[0].getType() == Type.SERVER) {
localFile[0] = null;
}
return Status.OK_STATUS;
}
// if it is server mapping
localFile[0] = pathMapper.getServerFile(remoteFile);
if (localFile[0] != null) {
localFile[0] = null;
return Status.OK_STATUS;
}
VirtualPath abstractPath = new VirtualPath(remoteFile);
// Check whether we have an exact mapping for the remote path
// If so - we shouldn't proceed with search (we should have this
// file right in the mapped folder)
VirtualPath testPath = abstractPath.clone();
testPath.removeLastSegment();
if (pathMapper.getLocalPathMapping(testPath) != null) {
return Status.OK_STATUS;
}
List<PathEntry> results = new LinkedList<PathEntry>();
IncludePath[] includePaths;
IBuildpathEntry[] buildPaths = null;
// Search in the whole workspace:
Set<IncludePath> s = new LinkedHashSet<IncludePath>();
Set<IBuildpathEntry> b = new LinkedHashSet<IBuildpathEntry>();
IProject[] projects = null;
if (currentProject != null && currentProject.isOpen()) {
projects = new IProject[] { currentProject };
} else {
projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
}
for (IProject project : projects) {
if (project.isOpen() && project.isAccessible()) {
// get include paths of all projects
PHPSearchEngine.buildIncludePath(project, s);
// get build paths of all projects
IScriptProject scriptProject = DLTKCore.create(project);
if (scriptProject != null && scriptProject.isOpen()) {
try {
IBuildpathEntry[] rawBuildpath = scriptProject.getRawBuildpath();
for (IBuildpathEntry pathEntry : rawBuildpath) {
b.add(pathEntry);
}
} catch (ModelException e) {
PHPDebugPlugin.log(e);
}
}
}
}
includePaths = s.toArray(new IncludePath[s.size()]);
buildPaths = b.toArray(new IBuildpathEntry[b.size()]);
// Try to find this file in the Workspace:
try {
IPath path = Path.fromOSString(remoteFile);
IFile file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(path);
if (file != null && file.exists()) {
for (IncludePath includePath : includePaths) {
if (includePath.getEntry() instanceof IContainer) {
IContainer container = (IContainer) includePath.getEntry();
if (container.getFullPath().isPrefixOf(file.getFullPath())) {
localFile[0] = new PathEntry(file.getFullPath().toString(), Type.WORKSPACE,
file.getParent());
pathMapper.addEntry(remoteFile, localFile[0], MappingSource.ENVIRONMENT);
PathMapperRegistry.storeToPreferences();
return Status.OK_STATUS;
}
}
}
}
} catch (Exception e) {
// no need to catch - this may be due to IPath creation
// failure
}
// Try to find this file in the Include Path:
File file = new File(remoteFile);
if (file.exists()) {
try {
IScriptProject scriptProject = DLTKCore.create(currentProject);
if (currentProject != null && scriptProject != null) {
IBuildpathEntry[] rawBuildpath = scriptProject.getRawBuildpath();
IEnvironment environment = EnvironmentManager.getEnvironment(currentProject);
IPath remoteFilePath = EnvironmentPathUtils.getFullPath(environment, new Path(remoteFile));
for (IBuildpathEntry entry : rawBuildpath) {
IPath entryPath = entry.getPath();
if (entry.getEntryKind() == IBuildpathEntry.BPE_VARIABLE) {
entryPath = DLTKCore.getResolvedVariablePath(entryPath);
}
if (entryPath != null && (entryPath.isPrefixOf(Path.fromOSString(remoteFile))
|| entryPath.isPrefixOf(remoteFilePath))) {
Type type = (entry.getEntryKind() == IBuildpathEntry.BPE_VARIABLE)
? Type.INCLUDE_VAR : Type.INCLUDE_FOLDER;
localFile[0] = new PathEntry(file.getAbsolutePath(), type, entry);
return Status.OK_STATUS;
}
}
}
} catch (Exception e) {
PHPDebugPlugin.log(e);
}
for (IncludePath includePath : includePaths) {
if (includePath.getEntry() instanceof IBuildpathEntry) {
IBuildpathEntry entry = (IBuildpathEntry) includePath.getEntry();
IPath entryPath = entry.getPath();
if (entry.getEntryKind() == IBuildpathEntry.BPE_VARIABLE) {
entryPath = DLTKCore.getResolvedVariablePath(entryPath);
}
if (entryPath != null && entryPath.isPrefixOf(Path.fromOSString(remoteFile))) {
Type type = (entry.getEntryKind() == IBuildpathEntry.BPE_VARIABLE) ? Type.INCLUDE_VAR
: Type.INCLUDE_FOLDER;
localFile[0] = new PathEntry(file.getAbsolutePath(), type, entry);
pathMapper.addEntry(remoteFile, localFile[0], MappingSource.ENVIRONMENT);
PathMapperRegistry.storeToPreferences();
return Status.OK_STATUS;
}
}
}
}
// try to find in build paths
if (buildPaths != null) {
for (IBuildpathEntry entry : buildPaths) {
IPath entryPath = entry.getPath();
if (entry.getEntryKind() == IBuildpathEntry.BPE_LIBRARY) {
// We don't support lookup in archive
File entryDir = entryPath.toFile();
find(entryDir, abstractPath, entry, results);
} else if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT
|| entry.getEntryKind() == IBuildpathEntry.BPE_SOURCE) {
IResource res = ResourcesPlugin.getWorkspace().getRoot()
.findMember(entry.getPath().lastSegment());
if (res instanceof IProject) {
IProject project = (IProject) res;
if (project.isOpen() && project.isAccessible()) {
try {
find(project, abstractPath, results);
} catch (InterruptedException e) {
PHPDebugPlugin.log(e);
}
}
}
} else if (entry.getEntryKind() == IBuildpathEntry.BPE_VARIABLE) {
entryPath = DLTKCore.getResolvedVariablePath(entryPath);
if (entryPath != null) {
File entryDir = entryPath.toFile();
find(entryDir, abstractPath, entry, results);
}
} else if (entry.getEntryKind() == IBuildpathEntry.BPE_CONTAINER) {
try {
if (projects.length == 0) {
continue;
}
final IProject currentProject = projects[0];
final IScriptProject scriptProject = DLTKCore.create(currentProject);
IBuildpathContainer container = DLTKCore.getBuildpathContainer(entry.getPath(),
scriptProject);
if (container != null) {
IBuildpathEntry[] buildpathEntries = container.getBuildpathEntries();
entryPath = EnvironmentPathUtils.getLocalPath(buildpathEntries[0].getPath());
if (entryPath != null) {
find(entryPath.toFile(), abstractPath, entry, results);
}
}
} catch (ModelException e) {
PHPCorePlugin.log(e);
}
}
}
}
// Iterate over all include path, and search for a requested
// file
for (IncludePath includePath : includePaths) {
if (includePath.getEntry() instanceof IContainer) {
try {
find((IContainer) includePath.getEntry(), abstractPath, results);
} catch (InterruptedException e) {
PHPDebugPlugin.log(e);
}
}
}
boolean foundInWorkspace = results.size() > 0;
// search in opened editors
searchOpenedEditors(results, abstractPath);
// Remove duplicated entries
results = new LinkedList<PathEntry>(new LinkedHashSet<PathEntry>(results));
if (!foundInWorkspace && results.size() == 1 && abstractPath.equals(results.get(0).getAbstractPath())) {
localFile[0] = results.get(0);
} else if (results.size() > 0) {
Collections.sort(results, new BestMatchPathComparator(abstractPath));
results = preFilterItems(abstractPath, results, debugTarget);
boolean isSingleMatch = results.size() == 1;
if (isSingleMatch) {
localFile[0] = results.get(0);
} else {
localFile[0] = filterItems(abstractPath, results.toArray(new PathEntry[results.size()]),
debugTarget);
}
if (localFile[0] != null) {
if (localFile[0].getType() == Type.SERVER) {
pathMapper.addServerEntry(remoteFile, localFile[0],
isSingleMatch ? MappingSource.ENVIRONMENT : MappingSource.USER);
PathMapperRegistry.storeToPreferences();
localFile[0] = null;
} else {
pathMapper.addEntry(remoteFile, localFile[0],
isSingleMatch ? MappingSource.ENVIRONMENT : MappingSource.USER);
PathMapperRegistry.storeToPreferences();
}
}
}
return Status.OK_STATUS;
}
};
findJob.schedule();
try {
findJob.join();
} catch (InterruptedException e) {
}
return localFile[0];
}
private static List<PathEntry> preFilterItems(VirtualPath remotePath, List<PathEntry> entries,
IDebugTarget debugTarget) {
final List<PathEntry> filtered = new LinkedList<PathEntry>();
final List<PathEntry> mostMatchEntries = getMostMatchEntries(entries, remotePath);
PathEntry matchByConfig = null;
if (mostMatchEntries.size() == 0 || mostMatchEntries.size() > 1) {
matchByConfig = getMatchFromLaunchConfiguration(entries, debugTarget.getLaunch().getLaunchConfiguration());
}
if (mostMatchEntries.size() == 1) {
filtered.add(mostMatchEntries.get(0));
} else if (matchByConfig != null) {
filtered.add(matchByConfig);
} else {
// Nothing was pre-filtered
return entries;
}
return filtered;
}
private static PathEntry getMatchFromLaunchConfiguration(List<PathEntry> entries,
ILaunchConfiguration launchConfiguration) {
try {
String projectName = launchConfiguration.getAttribute(IPHPDebugConstants.PHP_Project, (String) null);
if (projectName != null) {
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
for (PathEntry pathEntry : entries) {
Object container = pathEntry.getContainer();
if (container instanceof IContainer) {
IProject p = ((IContainer) container).getProject();
if (p != null && p.equals(project)) {
return pathEntry;
}
}
}
}
} catch (CoreException e) {
// log exception and continue debugging
PHPDebugPlugin.log(e);
}
return null;
}
private static List<PathEntry> getMostMatchEntries(List<PathEntry> entries, VirtualPath remotePath) {
if (remotePath.getSegmentsCount() == 1) {
return entries;
}
Map<Integer, List<PathEntry>> map = new HashMap<Integer, List<PathEntry>>();
int mostMatchSegmentsNumber = 1;
for (int i = 0; i < entries.size(); i++) {
PathEntry pathEntry = entries.get(i);
VirtualPath virtualPath = pathEntry.getAbstractPath();
int matchSegmentsNumber = getMatchSegmentsNumber(virtualPath, remotePath);
if (matchSegmentsNumber > mostMatchSegmentsNumber) {
mostMatchSegmentsNumber = matchSegmentsNumber;
}
List<PathEntry> list = map.get(matchSegmentsNumber);
if (list == null) {
list = new ArrayList<PathEntry>();
map.put(matchSegmentsNumber, list);
}
list.add(pathEntry);
}
List<PathEntry> mostMatchList = map.get(mostMatchSegmentsNumber);
return mostMatchList;
}
private static int getMatchSegmentsNumber(VirtualPath virtualPath, VirtualPath remotePath) {
int i = virtualPath.getSegmentsCount();
if (i > remotePath.getSegmentsCount()) {
i = remotePath.getSegmentsCount();
}
int result = 0;
// compare last i segments.
for (int j = i - 1; j >= 0; j--) {
if (virtualPath.getSegments()[j + (virtualPath.getSegmentsCount() - i)]
.equals(remotePath.getSegments()[j + (remotePath.getSegmentsCount() - i)])) {
result++;
}
}
return result;
}
private static void searchOpenedEditors(List<PathEntry> results, VirtualPath remotePath) {
// Collect open editor references:
List<IEditorReference> editors = new ArrayList<IEditorReference>(0);
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
for (IWorkbenchWindow element : windows) {
IWorkbenchPage[] pages = element.getPages();
for (IWorkbenchPage element2 : pages) {
IEditorReference[] references = element2.getEditorReferences();
editors.addAll(Arrays.asList(references));
}
}
// Collect external files opened in editors:
for (IEditorReference editor : editors) {
IEditorInput editorInput = null;
try {
editorInput = editor.getEditorInput();
} catch (PartInitException e) {
continue;
}
if (editorInput instanceof FileStoreEditorInput) {
File file = new File(((IURIEditorInput) editorInput).getURI());
if (file.exists() && file.getName().equalsIgnoreCase(remotePath.getLastSegment())) {
results.add(new PathEntry(file.getAbsolutePath(), PathEntry.Type.EXTERNAL, file.getParentFile()));
}
}
}
}
private static PathEntry filterItems(VirtualPath remotePath, PathEntry[] entries, IDebugTarget debugTarget) {
IPathEntryFilter[] filters = initializePathEntryFilters();
for (int i = 0; i < filters.length; ++i) {
entries = filters[i].filter(entries, remotePath, debugTarget);
}
return entries.length > 0 ? entries[0] : null;
}
private static synchronized IPathEntryFilter[] initializePathEntryFilters() {
if (filters == null) {
Map<String, IPathEntryFilter> filtersMap = new HashMap<String, IPathEntryFilter>();
IExtensionRegistry registry = Platform.getExtensionRegistry();
IConfigurationElement[] elements = registry.getConfigurationElementsFor(PHPDebugPlugin.getID(),
"pathEntryFilters"); //$NON-NLS-1$
for (IConfigurationElement element : elements) {
if ("filter".equals(element.getName())) { //$NON-NLS-1$
String id = element.getAttribute("id"); //$NON-NLS-1$
if (!filtersMap.containsKey(id)) {
String overridesIds = element.getAttribute("overridesId"); //$NON-NLS-1$
if (overridesIds != null) {
StringTokenizer st = new StringTokenizer(overridesIds, ", "); //$NON-NLS-1$
while (st.hasMoreTokens()) {
filtersMap.put(st.nextToken(), null);
}
}
try {
filtersMap.put(id, (IPathEntryFilter) element.createExecutableExtension("class")); //$NON-NLS-1$
} catch (CoreException e) {
PHPDebugPlugin.log(e);
}
}
}
}
Collection<IPathEntryFilter> l = filtersMap.values();
while (l.remove(null))
; // remove null elements
filters = l.toArray(new IPathEntryFilter[filtersMap.size()]);
}
return filters;
}
/**
* Searches for the path in the given IO file
*
* @param file
* File to start search from
* @param path
* Abstract path of the remote file
* @param container
* Include path entry container
* @param results
* List of results to return
* @throws InterruptedException
*/
private static void find(final File file, final VirtualPath path, final IBuildpathEntry container,
final List<PathEntry> results) {
if (!file.isDirectory() && file.getName().equals(path.getLastSegment())) {
Type type = (container.getEntryKind() == IBuildpathEntry.BPE_VARIABLE) ? Type.INCLUDE_VAR
: Type.INCLUDE_FOLDER;
PathEntry pathEntry = new PathEntry(file.getAbsolutePath(), type, container);
results.add(pathEntry);
return;
} else {
File[] files = file.listFiles(PHP_FILTER);
if (files != null) {
for (int i = 0; i < files.length; ++i) {
find(files[i], path, container, results);
}
}
}
}
/**
* Searches for the path in the given resource
*
* @param resource
* Resource to start the search from
* @param path
* Abstract path of the remote file
* @param results
* List of results to return
* @throws InterruptedException
*/
private static void find(final IResource resource, final VirtualPath path, final List<PathEntry> results)
throws InterruptedException {
if (resource == null || !resource.exists() || !resource.isAccessible()) {
return;
}
WorkspaceJob findJob = new WorkspaceJob("") { //$NON-NLS-1$
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
resource.accept(new IResourceVisitor() {
public boolean visit(IResource resource) throws CoreException {
if (!resource.isAccessible()) {
return false;
}
if (resource instanceof IFile && resource.getName().equals(path.getLastSegment())) {
PathEntry pathEntry = new PathEntry(resource.getFullPath().toString(), Type.WORKSPACE,
resource.getParent());
results.add(pathEntry);
}
return true;
}
});
return Status.OK_STATUS;
}
};
findJob.schedule();
findJob.join();
}
private static class PHPFilenameFilter implements FileFilter, IContentTypeChangeListener {
private Pattern phpFilePattern;
public PHPFilenameFilter() {
buildPHPFilePattern();
Platform.getContentTypeManager().addContentTypeChangeListener(this);
}
private void buildPHPFilePattern() {
IContentType type = Platform.getContentTypeManager().getContentType(ContentTypeIdForPHP.ContentTypeID_PHP);
String[] phpExtensions = type.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
StringBuilder buf = new StringBuilder();
buf.append(".*\\.("); //$NON-NLS-1$
for (int i = 0; i < phpExtensions.length; ++i) {
if (i > 0) {
buf.append("|"); //$NON-NLS-1$
}
buf.append(phpExtensions[i]);
}
buf.append(')');
phpFilePattern = Pattern.compile(buf.toString(), Pattern.CASE_INSENSITIVE);
}
public void contentTypeChanged(ContentTypeChangeEvent event) {
buildPHPFilePattern();
}
public boolean accept(File pathname) {
if (pathname.isDirectory() || phpFilePattern.matcher(pathname.getName()).matches()) {
return true;
}
return false;
}
}
}