/******************************************************************************* * Copyright (c) 2009, 2014 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 * Dawid PakuĊ‚a [456902] *******************************************************************************/ package org.eclipse.php.internal.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.dltk.ast.Modifiers; import org.eclipse.dltk.core.*; import org.eclipse.dltk.core.index2.search.ISearchEngine.MatchRule; import org.eclipse.dltk.core.search.IDLTKSearchScope; import org.eclipse.dltk.core.search.SearchEngine; import org.eclipse.dltk.internal.core.ModelManager; import org.eclipse.dltk.internal.core.search.ProjectIndexerManager; import org.eclipse.php.core.PHPToolkitUtil; import org.eclipse.php.core.libfolders.LibraryFolderManager; import org.eclipse.php.internal.core.includepath.IncludePathManager; import org.eclipse.php.internal.core.language.LanguageModelInitializer; import org.eclipse.php.internal.core.model.PHPModelAccess; import org.eclipse.php.internal.core.project.PHPNature; import org.eclipse.php.internal.core.util.ProjectBackwardCompatibilityUtil; import org.osgi.framework.BundleContext; import org.osgi.service.prefs.BackingStoreException; /** * The main plugin class to be used in the desktop. */ public class PHPCorePlugin extends Plugin { public static final String ID = "org.eclipse.php.core"; //$NON-NLS-1$ public static final int INTERNAL_ERROR = 10001; // The shared instance. private static PHPCorePlugin plugin; /** Whether the "PHP Toolkit" is initialized */ public static transient boolean toolkitInitialized; private final ListenerList<IShutdownListener> shutdownListeners = new ListenerList<>(); private ProjectConversionListener projectConvertListener = new ProjectConversionListener(); private ReindexOperationListener reindexOperationListener = new ReindexOperationListener(); /** * The constructor. */ public PHPCorePlugin() { super(); plugin = this; } /** * This method is called upon plug-in activation */ public void start(BundleContext context) throws Exception { super.start(context); initializeAfterStart(); } /** * This method is used for later initialization. This trick should release * plug-in start-up. */ void initializeAfterStart() { Job job = new Job("") { //$NON-NLS-1$ protected IStatus run(IProgressMonitor monitor) { // start the include path manager IncludePathManager.getInstance(); // start the library folder manager LibraryFolderManager.getInstance(); // register the listener in charge of converting the projects - // applies for projects being opened during work ResourcesPlugin.getWorkspace().addResourceChangeListener(projectConvertListener, IResourceChangeEvent.PRE_BUILD); ResourcesPlugin.getWorkspace().addResourceChangeListener(reindexOperationListener, IResourceChangeEvent.PRE_BUILD); // run the conversion over all the projects in the workspace - // all open projects will be converted try { convertProjects(monitor); } catch (CoreException e) { log(e); } checkStructureVersionAndReindex(); // startup symbolic links cache PHPSymbolicLinksCache.INSTANCE.startup(); return Status.OK_STATUS; } }; job.schedule(Job.LONG); } /** * Listener on changed projects, used for converting them into PDT 2.0.x * projects if needed */ private class ProjectConversionListener implements IResourceChangeListener { /* * Gathers all the projects that changed in the workspace and sends them * to the conversion method * * (non-Javadoc) * * @see * org.eclipse.core.resources.IResourceChangeListener#resourceChanged * (org.eclipse.core.resources.IResourceChangeEvent) */ public void resourceChanged(IResourceChangeEvent event) { IResourceDelta delta = event.getDelta(); IResourceDelta[] affectedChildren = delta.getAffectedChildren(); IProject[] projects = new IProject[affectedChildren.length]; for (int i = 0; i < affectedChildren.length; i++) { projects[i] = affectedChildren[i].getResource().getProject(); } try { processProjects(projects); } catch (CoreException e) { Logger.logException(e); } } } /** * This listener used for invoking re-index opeartion before project clean */ private class ReindexOperationListener implements IResourceChangeListener { public void resourceChanged(IResourceChangeEvent event) { if (event.getBuildKind() == IncrementalProjectBuilder.CLEAN_BUILD) { Object source = event.getSource(); try { if (source instanceof IProject) { IProject project = (IProject) source; ProjectIndexerManager.removeProject(project.getFullPath()); ProjectIndexerManager.indexProject(project); } else if (source instanceof IWorkspace) { IWorkspace workspace = (IWorkspace) source; reindexAllProjects(workspace); } } catch (CoreException e) { Logger.logException(e); } } } } /** * Check if model version is changed and re-index PHP projects if necessary. * * @see PHPCoreConstants.STRUCTURE_VERSION */ private void checkStructureVersionAndReindex() { IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(ID); String modelVersion = preferences.get(PHPCoreConstants.STRUCTURE_VERSION_PREFERENCE, null); if (PHPCoreConstants.STRUCTURE_VERSION.equals(modelVersion)) { return; } preferences.put(PHPCoreConstants.STRUCTURE_VERSION_PREFERENCE, PHPCoreConstants.STRUCTURE_VERSION); try { preferences.flush(); } catch (BackingStoreException e1) { Logger.logException(e1); } IWorkspace workspace = ResourcesPlugin.getWorkspace(); try { reindexAllProjects(workspace); } catch (ModelException e) { PHPCorePlugin.log(e); } catch (CoreException e) { PHPCorePlugin.log(e); } } private void reindexAllProjects(IWorkspace workspace) throws CoreException { IProject[] projects = workspace.getRoot().getProjects(); // remove from index: for (IProject project : projects) { if (!project.isAccessible()) { continue; } if (project.isOpen() && PHPToolkitUtil.isPHPProject(project)) { IScriptProject scriptProject = DLTKCore.create(project); IProjectFragment[] projectFragments = scriptProject.getProjectFragments(); for (IProjectFragment projectFragment : projectFragments) { ProjectIndexerManager.removeProjectFragment(scriptProject, projectFragment.getPath()); } ProjectIndexerManager.removeProject(project.getFullPath()); } } // add to index: for (IProject project : projects) { if (!project.isAccessible()) { continue; } ProjectIndexerManager.indexProject(project); } } /** * Gathers all the projects in the workspace and sends them to the * conversion method */ private void convertProjects(IProgressMonitor monitor) throws CoreException, ModelException { IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); processProjects(projects); } /** * Goes over the given projects and converts them */ private void processProjects(final IProject[] projects) throws CoreException, ModelException { ProjectsIterate: for (IProject project : projects) { // skip unaccessible projects if (!project.isAccessible()) { continue ProjectsIterate; } // verify that the project is a PHP project if (PHPToolkitUtil.isPHPProject(project)) { IProjectDescription projectDescription = project.getDescription(); ICommand[] commands = projectDescription.getBuildSpec(); // check if the Script Builder is installed for (int i = 0; i < commands.length; ++i) { if (commands[i].getBuilderName().equals(DLTKCore.BUILDER_ID)) { // when the builder exists - continue to the next // project continue ProjectsIterate; } } // perform modifications only if the builder is not installed modifyProject(project); } } } /* * Do the actual modifications on the project */ private void modifyProject(IProject project) throws CoreException, ModelException { final PHPNature phpNature = new PHPNature(); // add the required builders and build paths as defined in the new PHP // nature phpNature.setProject(project); phpNature.configure(); IScriptProject scriptProject = DLTKCore.create(project); // merge the project build path with the old include path IBuildpathEntry[] existingPath = scriptProject.getRawBuildpath(); ArrayList<IBuildpathEntry> newPath = new ArrayList<IBuildpathEntry>(); if (existingPath != null) { newPath.addAll(Arrays.asList(existingPath)); } ProjectBackwardCompatibilityUtil unit = new ProjectBackwardCompatibilityUtil(); IBuildpathEntry[] oldIncludePath = unit.convertIncludePathForProject(project); if (oldIncludePath != null) { newPath.addAll(Arrays.asList(oldIncludePath)); } scriptProject.setRawBuildpath(newPath.toArray(new IBuildpathEntry[newPath.size()]), new NullProgressMonitor()); } /** * Add listener that will be notified when this plug-in is going to shutdown * * @param listener */ public void addShutdownListener(IShutdownListener listener) { shutdownListeners.add(listener); } /** * This method is called when the plug-in is stopped */ public void stop(BundleContext context) throws Exception { Object[] listeners = shutdownListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { ((IShutdownListener) listeners[i]).shutdown(); } shutdownListeners.clear(); super.stop(context); ResourcesPlugin.getWorkspace().removeResourceChangeListener(projectConvertListener); ResourcesPlugin.getWorkspace().removeResourceChangeListener(reindexOperationListener); plugin = null; } /** * Returns the shared instance. */ public static PHPCorePlugin getDefault() { return plugin; } public static void log(IStatus status) { getDefault().getLog().log(status); } public static void log(Throwable e) { log(new Status(IStatus.ERROR, ID, INTERNAL_ERROR, "PHPCore plugin internal error", e)); //$NON-NLS-1$ } public static void logErrorMessage(String message) { log(new Status(IStatus.ERROR, ID, INTERNAL_ERROR, message, null)); } public static final boolean isDebugMode; static { String value = Platform.getDebugOption("org.eclipse.php.core/debug"); //$NON-NLS-1$ isDebugMode = value != null && value.equalsIgnoreCase("true"); //$NON-NLS-1$ } public static String getPluginId() { return ID; } /** * Helper method for returning one option value only. Equivalent to * <code>(String)PhpCore.getOptions().get(optionName)</code> Note that it * may answer <code>null</code> if this option does not exist. * <p> * For a complete description of the configurable options, see * <code>getDefaultOptions</code>. * </p> * * @param optionName * the name of an option * @return the String value of a given option * @see PhpCore#getDefaultOptions() * @see PhpCorePreferenceInitializer for changing default settings * @since 2.0 */ public static String getOption(String optionName) { return ModelManager.getModelManager().getOption(optionName); } /** * Returns the table of the current options. Initially, all options have * their default values, and this method returns a table that includes all * known options. * <p> * For a complete description of the configurable options, see * <code>getDefaultOptions</code>. * </p> * <p> * Returns a default set of options even if the platform is not running. * </p> * * @return table of current settings of all options (key type: * <code>String</code>; value type: <code>String</code>) * @see #getDefaultOptions() * @see JavaCorePreferenceInitializer for changing default settings */ public static Hashtable<String, String> getOptions() { return ModelManager.getModelManager().getOptions(); } /** * Initializes DLTKCore internal structures to allow subsequent operations * (such as the ones that need a resolved classpath) to run full speed. A * client may choose to call this method in a background thread early after * the workspace has started so that the initialization is transparent to * the user. * <p> * However calling this method is optional. Services will lazily perform * initialization when invoked. This is only a way to reduce initialization * overhead on user actions, if it can be performed before at some * appropriate moment. * </p> * <p> * This initialization runs accross all Java projects in the workspace. Thus * the workspace root scheduling rule is used during this operation. * </p> * <p> * This method may return before the initialization is complete. The * initialization will then continue in a background thread. * </p> * <p> * This method can be called concurrently. * </p> * * @param monitor * a progress monitor, or <code>null</code> if progress reporting * and cancellation are not desired * @exception CoreException * if the initialization fails, the status of the exception * indicates the reason of the failure * @since 3.1 */ public static void initializeAfterLoad(IProgressMonitor monitor) throws CoreException { try { if (monitor != null) { monitor.beginTask(CoreMessages.PHPCorePlugin_initializingPHPToolkit, 125); } // dummy query for waiting until the indexes are ready IDLTKSearchScope scope = SearchEngine.createWorkspaceScope(PHPLanguageToolkit.getDefault()); try { LanguageModelInitializer.cleanup(monitor); if (monitor != null) { monitor.subTask(CoreMessages.PHPCorePlugin_initializingSearchEngine); monitor.worked(25); } PHPModelAccess.getDefault().findMethods(ID, MatchRule.PREFIX, Modifiers.AccGlobal, 0, scope, monitor); if (monitor != null) { monitor.worked(25); } PHPModelAccess.getDefault().findTypes(ID, MatchRule.PREFIX, Modifiers.AccGlobal, 0, scope, monitor); if (monitor != null) { monitor.worked(25); } PHPModelAccess.getDefault().findFields(ID, MatchRule.PREFIX, Modifiers.AccGlobal, 0, scope, monitor); if (monitor != null) { monitor.worked(25); } PHPModelAccess.getDefault().findIncludes(ID, MatchRule.PREFIX, scope, monitor); if (monitor != null) { monitor.worked(25); } } catch (OperationCanceledException e) { if (monitor != null && monitor.isCanceled()) { throw e; } // else indexes were not ready: catch the exception so that jars // are still refreshed } } finally { if (monitor != null) { monitor.done(); } toolkitInitialized = true; } } }