/******************************************************************************* * 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; } } }