/******************************************************************************* * Copyright (c) 2004, 2008 John Krasnay 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: * John Krasnay - initial API and implementation *******************************************************************************/ package net.sf.vex.editor.config; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import net.sf.vex.core.ListenerList; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.jobs.ILock; import org.eclipse.swt.widgets.Display; /** * Singleton registry of configuration sources and listeners. * * The configuration sources may be accessed by multiple threads, and are * protected by a lock. All methods that modify or iterate over config sources * do so after acquiring the lock. Callers that wish to perform multiple * operations as an atomic transaction must lock and unlock the registry as * follows. * * <pre> * ConfigRegistry reg = ConfigRegistry.getInstance(); * try { * reg.lock(); * // make modifications * } finally { * reg.unlock(); * } * </pre> * * <p>This class also maintains a list of ConfigListeners. The * addConfigListener and removeConfigListener methods must be called from * the main UI thread. The fireConfigXXX methods may be called from other * threads; this class will ensure the listeners are called on the UI thread. */ public class ConfigRegistry { /** * Returns the singleton instance of the registry. */ public static ConfigRegistry getInstance() { return instance; } /** * Add a VexConfiguration to the list of configurations. * @param config VexConfiguration to be added. */ public void addConfigSource(ConfigSource config) { try { this.lock(); this.configs.add(config); } finally { this.unlock(); } } /** * Adds a ConfigChangeListener to the notification list. * @param listener Listener to be added. */ public void addConfigListener(IConfigListener listener) { this.configListeners.add(listener); } /** * Call the configChanged method on all registered ConfigChangeListeners. * The listeners are called from the display thread, even if this method * was called from another thread. * @param e ConfigEvent to be fired. */ public void fireConfigChanged(final ConfigEvent e) { if (this.isConfigLoaded()) { Runnable runnable = new Runnable() { public void run() { configListeners.fireEvent("configChanged", e); //$NON-NLS-1$ } }; Display display = Display.getDefault(); if (display.getThread() == Thread.currentThread()) { runnable.run(); } else { display.asyncExec(runnable); } } } /** * Call the configLoaded method on all registered ConfigChangeListeners * from the display thread. * This method is called from the ConfigLoaderJob thread. * @param e ConfigEvent to be fired. */ public void fireConfigLoaded(final ConfigEvent e) { // Runnable runnable = new Runnable() { // public void run() { // configLoaded = true; // configListeners.fireEvent("configLoaded", e); //$NON-NLS-1$ // } // }; // // Display.getDefault().asyncExec(runnable); configLoaded = true; configListeners.fireEvent("configLoaded", e); //$NON-NLS-1$ } /** * Returns an array of all config item factories. */ public IConfigItemFactory[] getAllConfigItemFactories() { List f = this.configItemFactories; return (IConfigItemFactory[]) f.toArray(new IConfigItemFactory[f.size()]); } /** * Returns an array of all registered ConfigItem objects implementing * the given extension point. * @param extensionPoint ID of the desired extension point. */ public List getAllConfigItems(String extensionPoint) { try { this.lock(); List items = new ArrayList(); for (Iterator it = this.configs.iterator(); it.hasNext(); ) { ConfigSource config = (ConfigSource) it.next(); items.addAll(config.getValidItems(extensionPoint)); } return items; } finally { this.unlock(); } } /** * Returns a list of all registered ConfigSource objects. * @return */ public List getAllConfigSources() { try { this.lock(); List result = new ArrayList(); result.addAll(this.configs); return result; } finally { this.unlock(); } } /** * Returns a specific configuration item given an extension point id * and the item's id. Returns null if either the extension point or * the item is not found. * * @param extensionPoint ID of the desired extension point. * @param id ID of the desired item. */ public ConfigItem getConfigItem(String extensionPoint, String id) { try { this.lock(); List items = this.getAllConfigItems(extensionPoint); for (Iterator it = items.iterator(); it.hasNext(); ) { ConfigItem item = (ConfigItem) it.next(); if (item.getUniqueId().equals(id)) { return item; } } return null; } finally { this.unlock(); } } /** * Returns the IConfigItemFactory object for the given extension point * or null if none exists. * @param extensionPointId Extension point ID for which to search. */ public IConfigItemFactory getConfigItemFactory(String extensionPointId) { for (Iterator it = this.configItemFactories.iterator(); it.hasNext();) { IConfigItemFactory factory = (IConfigItemFactory) it.next(); if (factory.getExtensionPointId().equals(extensionPointId)) { return factory; } } return null; } /** * Returns true if the Vex configuration has been loaded. * * @see net.sf.vex.editor.config.ConfigLoaderJob */ public boolean isConfigLoaded() { return this.configLoaded; } /** * Locks the registry for modification or iteration over its config sources. */ public void lock() { this.lock.acquire(); } /** * Remove a VexConfiguration from the list of configs. * @param config VexConfiguration to remove. */ public void removeConfigSource(ConfigSource config) { try { this.lock(); this.configs.remove(config); } finally { this.unlock(); } } /** * Removes a ConfigChangeListener from the notification list. * @param listener Listener to be removed. */ public void removeConfigListener(IConfigListener listener) { this.configListeners.remove(listener); } /** * Unlocks the registry. */ public void unlock() { this.lock.release(); } //======================================================== PRIVATE private static ConfigRegistry instance = new ConfigRegistry(); private ILock lock = Platform.getJobManager().newLock(); private List configs = new ArrayList(); private ListenerList configListeners = new ListenerList(IConfigListener.class, ConfigEvent.class); private boolean configLoaded = false; private List configItemFactories = new ArrayList(); /** * Class constructor. All initialization is performed here. */ private ConfigRegistry() { this.configItemFactories.add(new DoctypeFactory()); this.configItemFactories.add(new StyleFactory()); // TODO do we ever unregister this? ResourcesPlugin.getWorkspace().addResourceChangeListener(this.resourceChangeListener); } private IResourceChangeListener resourceChangeListener = new IResourceChangeListener() { public void resourceChanged(final IResourceChangeEvent event) { //System.out.println("resourceChanged, type is " + event.getType() + ", resource is " + event.getResource()); if (event.getType() == IResourceChangeEvent.PRE_CLOSE || event.getType() == IResourceChangeEvent.PRE_DELETE) { PluginProject pp = PluginProject.get((IProject) event.getResource()); if (pp != null) { //System.out.println(" removing project from config registry"); removeConfigSource(pp); fireConfigChanged(new ConfigEvent(this)); } } else if (event.getType() == IResourceChangeEvent.POST_CHANGE) { IResourceDelta[] resources = event.getDelta().getAffectedChildren(); for (int i = 0; i < resources.length; i++) { final IResourceDelta delta = resources[i]; if (delta.getResource() instanceof IProject) { final IProject project = (IProject) delta.getResource(); //System.out.println("Project " + project.getName() + " changed, isOpen is " + project.isOpen()); PluginProject pluginProject = PluginProject.get(project); boolean hasPluginProjectNature = false; try { hasPluginProjectNature = project.hasNature(PluginProjectNature.ID); } catch (CoreException ex) { // yup, sometimes checked exceptions really blow } if (!project.isOpen() && pluginProject != null) { //System.out.println(" closing project: " + project.getName()); removeConfigSource(pluginProject); fireConfigChanged(new ConfigEvent(this)); } else if (project.isOpen() && pluginProject == null && hasPluginProjectNature) { //System.out.println(" newly opened project: " + project.getName() + ", rebuilding"); // Must be run in another thread, since the workspace is locked here Runnable runnable = new Runnable() { public void run() { PluginProject.load(project); } }; Display.getDefault().asyncExec(runnable); } else { //System.out.println(" no action taken"); } } } } } }; }