/******************************************************************************* * Copyright (c) 2015 Zend Technologies 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: * Zend Technologies - initial API and implementation *******************************************************************************/ 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.dltk.core.*; import org.eclipse.dltk.core.environment.EnvironmentPathUtils; 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.SyncObject; 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.server.core.manager.ServersManager; /** * This search engine can be used to find local equivalents of a remote file * with the use of path mapping facility. If there is more than one possible * match (no mapping exists yet) then provided search result filter is used to * fetch the best match. * * @author Bartlomiej Laczkowski */ @SuppressWarnings("restriction") public class LocalFileSearchEngine { private 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; } } public static final String DEFAULT_FILE_SEARCH_FILTER = "org.eclipse.php.debug.openLocalFileSearchFilter"; //$NON-NLS-1$ private LocalFileSearchEngine.PHPFilenameFilter PHP_FILTER = new PHPFilenameFilter(); private final ILocalFileSearchFilter searchResultsFilter; public LocalFileSearchEngine() { this.searchResultsFilter = LocalFileSearchFilterRegistry.getFilter(DEFAULT_FILE_SEARCH_FILTER); } public LocalFileSearchEngine(ILocalFileSearchFilter searchResultsFilter) { this.searchResultsFilter = searchResultsFilter; } /** * Searches for the local file equivalent of the remote file in the given * workspace resource. * * @param container * Resource to start the search from * @param remoteFilePath * Abstract path of the remote file * @param serverUniqueId * Server unique ID (is used to get mappings for related server) * @return * @throws InterruptedException */ public LocalFileSearchResult find(final IResource container, final String remoteFilePath, final String serverUniqueId) throws InterruptedException { if (container == null || !container.exists() || !container.isAccessible()) { return null; } final PathMapper pathMapper = serverUniqueId != null ? PathMapperRegistry.getByServer(ServersManager.findServer(serverUniqueId)) : new PathMapper(); final VirtualPath abstractPath = new VirtualPath(remoteFilePath); final SyncObject<LocalFileSearchResult> searchResult = new SyncObject<LocalFileSearchResult>(); Job findJob = new Job(Messages.LocalFileSearchEngine_Searching_for_local_file) { protected IStatus run(IProgressMonitor monitor) { // First, look into the path mapper: LinkedList<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 = 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(remoteFilePath); 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())) { PathEntry localFile = new PathEntry(file.getFullPath().toString(), Type.WORKSPACE, file.getParent()); pathMapper.addEntry(remoteFilePath, localFile, MappingSource.ENVIRONMENT); PathMapperRegistry.storeToPreferences(); searchResult.set(new LocalFileSearchResult(localFile)); return Status.OK_STATUS; } } } } } catch (Exception e) { // no need to catch } // Try to find in build paths if (buildPaths != null) { for (IBuildpathEntry entry : buildPaths) { IPath entryPath = EnvironmentPathUtils.getLocalPath(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 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; if (!foundInWorkspace && results.size() == 1 && abstractPath.equals(results.getFirst().getAbstractPath())) { searchResult.set(new LocalFileSearchResult(results.getFirst())); } else if (results.size() > 0) { Collections.sort(results, new BestMatchPathComparator(abstractPath)); LocalFileSearchResult filteredResult = filter(results.toArray(new PathEntry[results.size()]), abstractPath, serverUniqueId); if (filteredResult.getPathEntry() != null && filteredResult.getStatus().isOK()) { pathMapper.addEntry(remoteFilePath, filteredResult.getPathEntry(), MappingSource.USER); PathMapperRegistry.storeToPreferences(); } searchResult.set(filteredResult); } return Status.OK_STATUS; } }; // Join the job... findJob.schedule(); try { findJob.join(); } catch (InterruptedException e) { } return searchResult.get(); } private LocalFileSearchResult filter(final PathEntry[] entries, final VirtualPath remotePath, final String serverUniqueId) { return searchResultsFilter.filter(entries, remotePath, serverUniqueId); } /** * 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 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(); } /** * 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 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); } } } } }