/******************************************************************************* * 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.preferences; import java.io.File; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent; import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import org.eclipse.core.runtime.preferences.InstanceScope; /** * PreferencesPropagator for propagation of preferences events that arrive as a * result from changes in the workspace preferences store and in the * project-specific preferences nodes. * * @author shalom */ public class PreferencesPropagator extends AbstractPreferencesPropagator { private Map<IProject, ProjectPreferencesPropagator> projectToPropagator; private Map<IProject, ProjectScope> projectToScope; private Map<IProject, INodeChangeListener> projectToNodeListener; private Map<IPreferenceChangeListener, IEclipsePreferences> preferenceChangeListeners; private String nodeQualifier; private IPreferenceChangeListener propertyChangeListener; /** * Constructs a new PreferencesPropagator. */ protected PreferencesPropagator(String nodeQualifier) { this.nodeQualifier = nodeQualifier; install(); } /** * Adds an IPreferencesPropagatorListener with a preferences key to listen * to. * * @param listener * An IPreferencesPropagatorListener. * @param preferencesKey * The preferences key that will screen the relevant changes. */ public void addPropagatorListener(IPreferencesPropagatorListener listener, String preferencesKey) { addNodeListener(listener.getProject(), getProjectScope(listener.getProject())); if (isProjectSpecific(listener.getProject(), preferencesKey)) { addToProjectPropagator(listener, preferencesKey); } else { super.addPropagatorListener(listener, preferencesKey); } } /** * Removes an IPreferencesPropagatorListener that was assigned to listen to * the given preferences key. * * @param listener * An IPreferencesPropagatorListener. * @param preferencesKey * The preferences key that is the screening key for the * IPreferencesPropagatorListener. */ public void removePropagatorListener(IPreferencesPropagatorListener listener, String preferencesKey) { if (isProjectSpecific(listener.getProject(), preferencesKey)) { removeFromProjectPropagator(listener, preferencesKey); } else { super.removePropagatorListener(listener, preferencesKey); } } /** * Sets a list of listeners for the given preferences key. This list will * replace any previous list of listeners for the key. * * @param listeners * A List of listeners that contains * IPreferencesPropagatorListeners. * @param preferencesKey * The preferences key that will screen the relevant changes. */ public void setPropagatorListeners(List<IPreferencesPropagatorListener> listeners, String preferencesKey) { super.setPropagatorListeners(listeners, preferencesKey); } /** * Install the preferences propagator. */ protected synchronized void install() { if (isInstalled) { return; } projectToPropagator = new HashMap<IProject, ProjectPreferencesPropagator>(); projectToScope = new HashMap<IProject, ProjectScope>(); projectToNodeListener = new HashMap<IProject, INodeChangeListener>(); preferenceChangeListeners = new HashMap<IPreferenceChangeListener, IEclipsePreferences>(); propertyChangeListener = new WorkspacePropertyChangeListener(); InstanceScope.INSTANCE.getNode(nodeQualifier).addPreferenceChangeListener(propertyChangeListener); super.install(); } /** * Uninstall the preferences propagator. */ protected synchronized void uninstall() { if (!isInstalled) { return; } InstanceScope.INSTANCE.getNode(nodeQualifier).removePreferenceChangeListener(propertyChangeListener); // remove the node listeners Iterator<IProject> projects = projectToNodeListener.keySet().iterator(); try { while (projects.hasNext()) { IProject project = projects.next(); ProjectScope scope = projectToScope.get(project); IEclipsePreferences node = scope.getNode(nodeQualifier); if (node != null) { ((IEclipsePreferences) node.parent()).removeNodeChangeListener(projectToNodeListener.get(project)); } } } catch (Exception e) { } // uninstall the project propagators Iterator<ProjectPreferencesPropagator> propagators = projectToPropagator.values().iterator(); while (propagators.hasNext()) { propagators.next().uninstall(); } preferenceChangeListeners = null; projectToScope = null; projectToPropagator = null; projectToNodeListener = null; super.uninstall(); } /* * Add the listener to a ProjectPreferencesPropagator. Create a new * propagator if needed. */ private void addToProjectPropagator(IPreferencesPropagatorListener listener, String preferencesKey) { ProjectPreferencesPropagator propagator = projectToPropagator.get(listener.getProject()); if (propagator == null) { propagator = new ProjectPreferencesPropagator(listener.getProject(), nodeQualifier); projectToPropagator.put(listener.getProject(), propagator); } propagator.addPropagatorListener(listener, preferencesKey); } /* * Removes a listener from a ProjectPreferencesPropagator. If not * ProjectPreferencesPropagator exists, nothing will happen. */ private void removeFromProjectPropagator(IPreferencesPropagatorListener listener, String preferencesKey) { ProjectPreferencesPropagator propagator = projectToPropagator.get(listener.getProject()); if (propagator != null) { propagator.removePropagatorListener(listener, preferencesKey); } } /* * Returns true if the given project has a specific settings for the given * key; false, otherwise. */ private boolean isProjectSpecific(IProject project, String preferencesKey) { ProjectScope projectScope = getProjectScope(project); IPath location = projectScope.getLocation(); if (location != null && new File(location.toOSString()).exists()) { return projectScope.getNode(nodeQualifier).get(preferencesKey, null) != null; } return false; } /* * Returns a ProjectScope for the given IProject. */ private ProjectScope getProjectScope(IProject project) { ProjectScope scope = projectToScope.get(project); if (scope == null) { scope = new ProjectScope(project); projectToScope.put(project, scope); } return scope; } /* * Adds a node listener to the parent node of the project preferences scope. */ private void addNodeListener(IProject project, ProjectScope projectScope) { IEclipsePreferences node = projectScope.getNode(nodeQualifier); if (node != null) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=510530 // A INodeChangeListener can already exist in // "projectToNodeListener" (i.e. nodeListener != null) when a // project is deleted and recreated (a unique IProject object // seems to be cached per project and "survives" creations and // deletions of this project), so reuse it... INodeChangeListener nodeListener = projectToNodeListener.get(project); if (nodeListener == null) { nodeListener = new InnerNodeChangeListener(project); } else { // by security ((IEclipsePreferences) node.parent()).removeNodeChangeListener(nodeListener); } // ...and don't forget to add it to the newest parent node ((IEclipsePreferences) node.parent()).addNodeChangeListener(nodeListener); projectToNodeListener.put(project, nodeListener); if (!preferenceChangeListeners.containsValue(node)) { IPreferenceChangeListener changeListener = new NodePreferenceChangeListener(project); node.addPreferenceChangeListener(changeListener); preferenceChangeListeners.put(changeListener, node); } } } private void notifyEvent(PreferencesPropagatorEvent event, String propertyKey, IProject project) { synchronized (lock) { if (project != null) { // The event arrived from the NodePreferenceChangeListener when // we moved to a project-specific settings. ProjectPreferencesPropagator ppp = projectToPropagator.get(project); if (ppp == null) { ppp = new ProjectPreferencesPropagator(project, nodeQualifier); projectToPropagator.put(project, ppp); } ppp.notifyPropagatorEvent(event); } else { List<IPreferencesPropagatorListener> list = getPropagatorListeners(propertyKey); if (list == null) return; Iterator<IPreferencesPropagatorListener> iterator = list.iterator(); while (iterator.hasNext()) { iterator.next().preferencesEventOccured(event); } } } } /* * An inner node change listener that should listen to any change in the * project-scope parent node and should make all the needed listener swaps. */ private class InnerNodeChangeListener implements IEclipsePreferences.INodeChangeListener { private IProject project; public InnerNodeChangeListener(IProject project) { this.project = project; } /* * When a node is added, there is a move for a project-specific * prefernces, thus, we should divert all the listeners for the project * to the ProjectPreferencesPropagator. */ public void added(NodeChangeEvent event) { IEclipsePreferences pNode = null; if (event.getChild() instanceof IEclipsePreferences) { pNode = (IEclipsePreferences) event.getChild(); } else { return; } if (!preferenceChangeListeners.containsValue(pNode)) { IPreferenceChangeListener changeListener = new NodePreferenceChangeListener(project); pNode.addPreferenceChangeListener(changeListener); preferenceChangeListeners.put(changeListener, pNode); } } /** * (non-Javadoc) * * @see org.eclipse.core.runtime.preferences.IEclipsePreferences$INodeChangeListener#removed(org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent) */ public void removed(NodeChangeEvent event) { Object childNode = event.getChild(); if (preferenceChangeListeners.containsValue(childNode)) { // search for the listener to be removed Iterator<IPreferenceChangeListener> keys = preferenceChangeListeners.keySet().iterator(); while (keys.hasNext()) { Object key = keys.next(); IEclipsePreferences aNode = preferenceChangeListeners.get(key); if (aNode == childNode) { preferenceChangeListeners.remove(key); return; } } } } } /* * An inner preferences node change listener */ public class NodePreferenceChangeListener implements IPreferenceChangeListener { private IProject project; public NodePreferenceChangeListener(IProject project) { this.project = project; } public void preferenceChange(PreferenceChangeEvent event) { String key = event.getKey(); String newValue = (String) event.getNewValue(); if (newValue == null) { // We are moving from the project scope to the workspace scope removeFromProjectPropagator(key, project); } else { // We are moving from the workspace to the project specific // preferences moveToProjectPropagator(key, project); } PreferencesPropagatorEvent e = new PreferencesPropagatorEvent(event.getSource(), event.getOldValue(), event.getNewValue(), event.getKey()); notifyEvent(e, key, (newValue == null) ? null : project); } } /* * Move the listeners to the project preferences propagator. */ private void moveToProjectPropagator(String key, IProject project) { List<IPreferencesPropagatorListener> list = null; IPreferencesPropagatorListener[] listeners = null; synchronized (lock) { list = getPropagatorListeners(key); if (list == null) { return; } listeners = new IPreferencesPropagatorListener[list.size()]; list.toArray(listeners); } for (IPreferencesPropagatorListener listener : listeners) { if (project.equals(listener.getProject())) { // Move the listener from this propagator to the project // specific preferences propagator super.removePropagatorListener(listener, key); addToProjectPropagator(listener, key); } } } /* * Remove the listeners from the project preferences propagator. */ private void removeFromProjectPropagator(String key, IProject project) { synchronized (lock) { ProjectPreferencesPropagator propagator = projectToPropagator.get(project); if (propagator != null) { // Remove the listeners from the project propagator and place // them in this propagator. List<IPreferencesPropagatorListener> listeners = propagator.removePropagatorListeners(key); if (listeners != null && listeners.size() > 0) { Iterator<IPreferencesPropagatorListener> iter = listeners.iterator(); while (iter.hasNext()) { addPropagatorListener(iter.next(), key); } } } } } /* * An inner IPropertyChangeListener that listens to the workspace changes * and notify all the registered listeners. */ private class WorkspacePropertyChangeListener implements IPreferenceChangeListener { @Override public void preferenceChange(PreferenceChangeEvent event) { // Collect and notify the listeners that are defined to listen to // workspace preferences changes // for the arrived event. PreferencesPropagatorEvent e = new PreferencesPropagatorEvent(event.getSource(), event.getOldValue(), event.getNewValue(), event.getKey()); notifyEvent(e, event.getKey(), null); } } }