/******************************************************************************* * 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.core.util; import java.io.File; import java.util.*; import java.util.regex.Pattern; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.dltk.core.*; import org.eclipse.dltk.core.environment.EnvironmentPathUtils; import org.eclipse.dltk.internal.core.ArchiveProjectFragment; import org.eclipse.php.internal.core.Logger; import org.eclipse.php.internal.core.PHPCorePlugin; import org.eclipse.php.internal.core.includepath.IIncludepathListener; import org.eclipse.php.internal.core.includepath.IncludePath; import org.eclipse.php.internal.core.includepath.IncludePathManager; import org.eclipse.php.internal.core.phar.PharConstants; import org.eclipse.php.internal.core.phar.PharPath; /** * This utility implements internal PHP mechanism for searching included files. * The algorithm is the following:<br/> * <br/> * * Files for including are first looked for in each <b>include_path</b> entry * relative to the current working directory, and then in the directory of * current script. E.g. if your <b>include_path</b> is libraries, current * working directory is /www/, you included include/a.php and there is include * "b.php" in that file, b.php is first looked in /www/libraries/ and then in * /www/include/. If filename begins with ./ or ../, it is looked only in the * current working directory. * * @author michael */ public class PHPSearchEngine implements IIncludepathListener { private static Pattern RELATIVE_PATH_PATTERN = Pattern.compile("\\.\\.?[/\\\\].*"); //$NON-NLS-1$ private Map<IProject, IncludePath[]> projectIncludePaths; private static PHPSearchEngine instance = new PHPSearchEngine(); private PHPSearchEngine() { projectIncludePaths = new HashMap<IProject, IncludePath[]>(); IncludePathManager.getInstance().registerIncludepathListener(this); } private static PHPSearchEngine getInstance() { return instance; } /** * Searches for the given path using internal PHP mechanism * * @param path * File path to resolve * @param currentWorkingDir * Current working directory (usually: CWD of PHP process), * absolute (workspace of file system) * @param currentScriptDir * Absolute (workspace of file system) directory of current * script (which is interpreted by the PHP at this time) * @param currentProject * Current project to which current script belongs * @return resolved path, or <code>null</code> in case of failure */ public static Result<?, ?> find(String path, String currentWorkingDir, String currentScriptDir, IProject currentProject) { return getInstance().internalFind(path, currentWorkingDir, currentScriptDir, currentProject); } public static Result<?, ?> find(String path, String currentWorkingDir, String currentScriptDir, IProject currentProject, Set<String> exclusiveFiles) { return getInstance().internalFind(path, currentWorkingDir, currentScriptDir, currentProject, exclusiveFiles); } private Result<?, ?> internalFind(String path, String currentWorkingDir, String currentScriptDir, IProject currentProject) { return internalFind(path, currentWorkingDir, currentScriptDir, currentProject, null); } private Result<?, ?> internalFind(String path, String currentWorkingDir, String currentScriptDir, IProject currentProject, Set<String> exclusiveFiles) { if (path == null || currentWorkingDir == null || currentScriptDir == null || currentProject == null) { throw new NullPointerException(Messages.PHPSearchEngine_1); } // check whether the path is absolute File file = new File(path); if (file.isAbsolute()) { return searchExternalOrWorkspaceFile(file); } if (RELATIVE_PATH_PATTERN.matcher(path).matches()) { // check whether // the path // starts with // ./ or ../ return searchExternalOrWorkspaceFile(currentWorkingDir, path); } List<Result> list = new ArrayList<PHPSearchEngine.Result>(); // look into include path: IncludePath[] includePaths = buildIncludePath(currentProject); for (IncludePath includePath : includePaths) { if (includePath.isBuildpath()) { Result<?, ?> searchInBuildpathEntry = searchInBuildpathEntry(path, (IBuildpathEntry) includePath.getEntry(), currentProject); if (searchInBuildpathEntry != null) { return searchInBuildpathEntry; } } else if (includePath.getEntry() instanceof IFile) { IFile resource = (IFile) includePath.getEntry(); Result result = new ResourceResult(resource); if (exclusiveFiles == null || (resource.getLocation() != null && !exclusiveFiles.contains(resource.getLocation().toOSString()))) { return result; } else { list.add(result); } } else if (includePath.getEntry() instanceof IContainer) { IContainer container = (IContainer) includePath.getEntry(); IResource resource = container.findMember(path); if ((resource instanceof IFile)) { Result result = new ResourceResult((IFile) resource); if (exclusiveFiles == null || (resource.getLocation() != null && !exclusiveFiles.contains(resource.getLocation().toOSString()))) { return result; } else { list.add(result); } } } else { Logger.log(Logger.ERROR, "Unknown IncludePath entry: " + includePath); //$NON-NLS-1$ } } if (!list.isEmpty()) { return list.get(0); } // look at current script directory: return searchExternalOrWorkspaceFile(currentScriptDir, path); } private static Result<?, ?> searchInBuildpathEntry(String path, IBuildpathEntry entry, IProject currentProject) { IPath entryPath = EnvironmentPathUtils.getLocalPath(entry.getPath()); if (entry.getEntryKind() == IBuildpathEntry.BPE_LIBRARY) { IScriptProject scriptProject = DLTKCore.create(currentProject); IProjectFragment[] projectFragments = scriptProject.findProjectFragments(entry); if (projectFragments != null && projectFragments.length > 0) { if (projectFragments[0] instanceof ArchiveProjectFragment) { ArchiveProjectFragment apf = (ArchiveProjectFragment) projectFragments[0]; boolean external = false; IPath apfp = apf.getPath(); if (EnvironmentPathUtils.isFull(apfp)) { apfp = EnvironmentPathUtils.getLocalPath(apfp); external = true; } PharPath pharPath = PharPath.getPharPath(new Path(path)); if (pharPath != null) { if (external && apfp.equals(new Path(pharPath.getPharName())) || !external && apfp.lastSegment().equals(new Path(pharPath.getPharName()).lastSegment())) { if (pharPath.isPhar()) { final String stubName = PharConstants.STUB_PATH; pharPath.setFolder(new Path(stubName).removeLastSegments(1).toString()); pharPath.setFile(new Path(stubName).lastSegment()); } IScriptFolder scriptFolder = apf.getScriptFolder(new Path(pharPath.getFolder())); try { IModelElement[] children = scriptFolder.getChildren(); if (children != null && children.length > 0) { for (int i = 0; i < children.length; i++) { if (((ISourceModule) children[i]).getElementName().equals(pharPath.getFile())) { return new IncludedPharFileResult(scriptFolder, (ISourceModule) children[i]); } } } } catch (ModelException e) { PHPCorePlugin.log(e); return null; } } } } } File entryDir = entryPath.toFile(); File file = new File(entryDir, path); if (file.exists()) { return new IncludedFileResult(entry, file); } } else if (entry.getEntryKind() == IBuildpathEntry.BPE_VARIABLE) { entryPath = DLTKCore.getResolvedVariablePath(entryPath); File entryDir = entryPath.toFile(); File file = new File(entryDir, path); if (file.exists()) { return new IncludedFileResult(entry, file); } } else if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT) { IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IProject project = workspaceRoot.getProject(entryPath.segment(0)); if (project.isAccessible()) { IScriptProject scriptProject = DLTKCore.create(project); try { for (IProjectFragment fragment : scriptProject.getProjectFragments()) { if (fragment.getResource() instanceof IFolder || fragment.getResource() instanceof IProject) { IResource resource = ((IContainer) fragment.getResource()).findMember(path); if (resource instanceof IFile) { return new ResourceResult((IFile) resource); } } } } catch (ModelException e) { } } } else if (entry.getEntryKind() == IBuildpathEntry.BPE_SOURCE) { IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IResource resource = workspaceRoot.findMember(entryPath); if (resource instanceof IContainer) { resource = ((IContainer) resource).findMember(path); if (resource instanceof IFile) { return new ResourceResult((IFile) resource); } } } else if (entry.getEntryKind() == IBuildpathEntry.BPE_CONTAINER) { try { IScriptProject scriptProject = DLTKCore.create(currentProject); IBuildpathContainer container = DLTKCore.getBuildpathContainer(entry.getPath(), scriptProject); if (container != null) { IBuildpathEntry[] buildpathEntries = container.getBuildpathEntries(); if (buildpathEntries != null) { for (IBuildpathEntry buildpathEntry : buildpathEntries) { Result<?, ?> result = searchInBuildpathEntry(path, buildpathEntry, currentProject); if (result != null) { IProjectFragment[] projectFragments = scriptProject.findProjectFragments(entry); ((IncludedFileResult) result).setProjectFragments(projectFragments); return result; } } } } } catch (ModelException e) { PHPCorePlugin.log(e); } } return null; } private static Result<?, ?> searchExternalOrWorkspaceFile(String directory, String relativeFile) { IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(directory); if (resource instanceof IContainer) { IContainer container = (IContainer) resource; IResource file = container.findMember(relativeFile); if (file instanceof IFile) { return new ResourceResult((IFile) file); } } File dir = new File(directory); if (dir.isDirectory()) { return searchExternalOrWorkspaceFile(new File(dir, relativeFile)); } return null; } private static Result<?, ?> searchExternalOrWorkspaceFile(File file) { if (file.exists()) { IFile res = ResourcesPlugin.getWorkspace().getRoot() .getFileForLocation(Path.fromOSString(file.getAbsolutePath())); if (res != null) { return new ResourceResult(res); } // for linked resources IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(file.toURI()); if (files != null && files.length != 0) { return new ResourceResult(files[0]); } return new ExternalFileResult(file); } return null; } /** * Builds include path for searching by the given project. Result contains * include path of the given project, referenced projects and their include * paths. * * @param project * Current project * @return array of include path objects (it can be one of: IContainer, * IncludePathEntry) */ public static IncludePath[] buildIncludePath(IProject project) { Set<IncludePath> results = new LinkedHashSet<IncludePath>(); buildIncludePath(project, results); return results.toArray(new IncludePath[results.size()]); } /** * Builds include path for searching by the given project. Result contains * include path of the given project, referenced projects and their include * paths. * * @param project * Current project * @param results * Array of include path objects (it can be one of: IContainer, * IncludePathEntry) */ public static void buildIncludePath(IProject project, Set<IncludePath> results) { if (results.contains(project)) { return; } if (!project.isAccessible() || !project.isOpen()) { return; } // Collect include paths: results.addAll(Arrays.asList(getInstance().getProjectIncludePath(project))); } private IncludePath[] getProjectIncludePath(IProject project) { IncludePath[] includePaths = projectIncludePaths.get(project); if (includePaths == null) { includePaths = IncludePathManager.getInstance().getIncludePaths(project); projectIncludePaths.put(project, includePaths); } return includePaths; } public void refresh(IProject project) { IncludePath[] includePaths = IncludePathManager.getInstance().getIncludePaths(project); projectIncludePaths.put(project, includePaths); } /** * Result returned by PHP search engine */ abstract public static class Result<T, S> { private T container; private S file; public Result(T container, S file) { this.container = container; this.file = file; } public T getContainer() { return container; } public S getFile() { return file; } public void setContainer(T container) { this.container = container; } public void setFile(S file) { this.file = file; } } /** * Result for Workspace file */ public static class ResourceResult extends Result<Object, IFile> { public ResourceResult(IFile file) { super(file.getParent(), file); } } /** * Result for included file (from Include Path) */ public static class IncludedFileResult extends Result<IBuildpathEntry, File> { private IProjectFragment[] projectFragments; public IncludedFileResult(IBuildpathEntry container, File file) { super(container, file); } public void setProjectFragments(IProjectFragment[] projectFragments) { this.projectFragments = projectFragments; } public IProjectFragment[] getProjectFragments() { return projectFragments; } } /** * Result for included file (from Include Path) */ public static class IncludedPharFileResult extends Result<IScriptFolder, ISourceModule> { public IncludedPharFileResult(IScriptFolder container, ISourceModule file) { super(container, file); } } /** * Result for external file (on file system) */ public static class ExternalFileResult extends Result<Object, File> { public ExternalFileResult(File file) { super(file.getParentFile(), file); } } }