/*******************************************************************************
 * Copyright (c) 2001, 2018 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.sse.ui.internal;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IKeyBindingService;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.part.MultiPageEditorSite;
import org.eclipse.ui.texteditor.IUpdate;
import org.eclipse.wst.sse.ui.internal.extension.ActionDescriptor;
import org.eclipse.wst.sse.ui.internal.extension.IExtendedEditorActionProxyForDelayLoading;
import org.eclipse.wst.sse.ui.internal.extension.RegistryReader;
import org.eclipse.wst.sse.ui.internal.provisional.extensions.ISourceEditingTextTools;


/**
 * This class reads the registry for extensions that plug into 'editorActions'
 * extension point.
 */

public class ExtendedEditorActionBuilder extends RegistryReader {

	public class ExtendedContributor implements IExtendedContributor, IMenuListener {
		private IExtendedSimpleEditor activeExtendedEditor = null;

		private List cache;
		private Map map = new HashMap();
		private IMenuManager menuBar = null;

		private Set menus = new HashSet();

		public ExtendedContributor(List cache) {
			this.cache = cache;
		}

		private IExtendedSimpleEditor computeExtendedEditor(final IEditorPart editor) {
			IExtendedSimpleEditor simpleEditor = null;
			if (editor instanceof IExtendedSimpleEditor) {
				simpleEditor = (IExtendedSimpleEditor) editor;
			}
			if (editor != null && simpleEditor == null) {
				final ISourceEditingTextTools tools = editor.getAdapter(ISourceEditingTextTools.class);
				if (tools != null) {
					simpleEditor = new IExtendedSimpleEditor() {
						public int getCaretPosition() {
							return tools.getCaretOffset();
						}

						public IDocument getDocument() {
							return tools.getDocument();
						}

						public IEditorPart getEditorPart() {
							return tools.getEditorPart();
						}

						public Point getSelectionRange() {
							ITextSelection selection = tools.getSelection();
							return new Point(selection.getOffset(), selection.getOffset() + selection.getLength());
						}

					};
				}
			}
			return simpleEditor;
		}

		public void contributeToMenu(IMenuManager menu) {
			menuBar = menu;
			long time0 = System.currentTimeMillis();
			for (int i = 0; i < cache.size(); i++) {
				Object obj = cache.get(i);
				if (obj instanceof IConfigurationElement) {
					IConfigurationElement menuElement = (IConfigurationElement) obj;
					if ((menuElement.getName()).equals(TAG_MENU)) {
						contributeMenu(menuElement, menu, true);
						if (debugMenu)
							System.out.println(getClass().getName() + "#contributeToMenu() added: " + menuElement.getAttribute(ATT_ID)); //$NON-NLS-1$
					}
				}
				else if (obj instanceof ActionDescriptor) {
					try {
						ActionDescriptor ad = (ActionDescriptor) obj;
						IMenuManager mm = contributeMenuAction(ad, menu, true, false);
						if (mm != null) {
							map.put(ad.getContributionItem(), mm);
							mm.addMenuListener(this);
							menus.add(mm);
							if (debugMenu)
								System.out.println(getClass().getName() + "#contributeToMenu() added: " + ad.getId()); //$NON-NLS-1$
						}
					}
					catch (Exception e) {
						Logger.logException("contributing to menu", e); //$NON-NLS-1$
					}
				}
			}
			if (debugContributeTime)
				System.out.println(getClass().getName() + "#contributeToMenu(): ran in " + (System.currentTimeMillis() - time0) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
		}

		public void contributeToPopupMenu(IMenuManager menu) {
			long time0 = System.currentTimeMillis();
			for (int i = 0; i < cache.size(); i++) {
				Object obj = cache.get(i);
				if (obj instanceof IConfigurationElement) {
					IConfigurationElement menuElement = (IConfigurationElement) obj;
					if ((menuElement.getName()).equals(TAG_POPUPMENU)) {
						contributeMenu(menuElement, menu, true);
					}
				}
				else if (obj instanceof ActionDescriptor) {
					try {
						ActionDescriptor ad = (ActionDescriptor) obj;
						IAction a = ad.getAction();
						if (a instanceof IExtendedEditorAction) {
							// uncaught exceptions could cause the menu to not
							// be shown
							try {
								if (((ad.getPopupMenuPath() != null) || (ad.getPopupMenuGroup() != null)) && (a instanceof IExtendedEditorActionProxyForDelayLoading)) {
								    ((IExtendedEditorActionProxyForDelayLoading)a).realize();
								}

								IExtendedEditorAction eea = (IExtendedEditorAction) a;
								eea.setActiveExtendedEditor(activeExtendedEditor);
								eea.update();
								if (eea.isVisible()) {
									IMenuManager parent = contributeMenuAction(ad, menu, true, true);
									if (debugPopup && parent != null)
										System.out.println(getClass().getName() + "#contributeToPopupMenu() added: " + ad.getId()); //$NON-NLS-1$
								}
							}
							catch (Exception e) {
								Logger.logException(e);
							}

						}
						else {
							IMenuManager parent = contributeMenuAction(ad, menu, true, true);
							if (debugPopup && parent != null)
								System.out.println(getClass().getName() + "#contributeToPopupMenu() added: " + ad.getId()); //$NON-NLS-1$
						}
					}
					catch (Exception e) {
						Logger.logException("contributing to popup", e); //$NON-NLS-1$
					}
				}
			}
			if (debugContributeTime)
				System.out.println(getClass().getName() + "#contributeToPopupMenu(): ran in " + (System.currentTimeMillis() - time0) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
		}

		public void contributeToStatusLine(IStatusLineManager manager) {
			// nothing from here
		}

		public void contributeToToolBar(IToolBarManager manager) {
			long time0 = System.currentTimeMillis();
			for (int i = 0; i < cache.size(); i++) {
				Object obj = cache.get(i);
				if (obj instanceof ActionDescriptor) {
					try {
						ActionDescriptor ad = (ActionDescriptor) obj;
						IAction a = ad.getAction();
						if (a instanceof IExtendedEditorAction) {
							if (((ad.getToolbarPath() != null) || (ad.getToolbarGroup() != null)) && (a instanceof IExtendedEditorActionProxyForDelayLoading)) {
							    ((IExtendedEditorActionProxyForDelayLoading)a).realize();
							}
							IExtendedEditorAction eea = (IExtendedEditorAction) a;
							eea.setActiveExtendedEditor(activeExtendedEditor);
							eea.update();
							if (eea.isVisible()) {
								boolean contributed = contributeToolbarAction(ad, manager, true);
								if (debugToolbar && contributed)
									System.out.println(getClass().getName() + "#contributeToToolBar() added: " + ad.getId()); //$NON-NLS-1$
							}
							else {
								if (debugToolbar)
									System.out.println(getClass().getName() + "#contributeToToolBar(): [skipped] " + ad.getId()); //$NON-NLS-1$
							}
						}
						else {
							boolean contributed = contributeToolbarAction(ad, manager, true);
							if (debugToolbar && contributed)
								System.out.println(getClass().getName() + "#contributeToToolBar() added: " + ad.getId()); //$NON-NLS-1$
						}
					}
					catch (Exception e) {
						Logger.logException("contributing to toolbar", e); //$NON-NLS-1$
					}
				}
			}
			if (debugContributeTime)
				System.out.println(getClass().getName() + "#contributeToToolBar(): ran in " + (System.currentTimeMillis() - time0) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
		}

		public void dispose() {
			Iterator it = menus.iterator();
			while (it.hasNext()) {
				Object o = it.next();
				if (o instanceof IMenuManager) {
					((IMenuManager) o).removeMenuListener(this);
				}
			}
		}

		public void init(IActionBars bars, IWorkbenchPage page) {
			// nothing from here
		}

		public void menuAboutToShow(IMenuManager menu) {
			// slows down the menu and interferes with it for other editors;
			// optimize on visibility
			IEditorSite site = null;
			boolean activeEditorIsVisible = false;

			if (activeExtendedEditor != null && activeExtendedEditor.getEditorPart() != null)
				site = activeExtendedEditor.getEditorPart().getEditorSite();
			if (site == null)
				return;

			// Eclipse bug 48784 - [MPE] ClassCast exception Workbench page
			// isPartVisiable for MultiPageSite
			if (site instanceof MultiPageEditorSite) {
				Object multiPageEditor = ((MultiPageEditorSite) site).getMultiPageEditor();
				activeEditorIsVisible = multiPageEditor.equals(site.getPage().getActiveEditor()) || multiPageEditor.equals(site.getPage().getActivePart());
			}
			else {
				activeEditorIsVisible = site.getWorkbenchWindow().getPartService().getActivePart().equals(activeExtendedEditor.getEditorPart());
			}
			// due to a delay class loading, don't return now
//			if (!activeEditorIsVisible)
//				return;

			IContributionItem[] items = menu.getItems();
			if (items == null || items.length == 0)
				return;

			for (int i = 0; i < items.length; ++i) {
				// add menu listener to submenu
				if (items[i] instanceof IMenuManager) {
					((IMenuManager) items[i]).addMenuListener(this);
					menus.add(items[i]);
				}
			}

			Set keys = map.keySet();
			Iterator it = keys.iterator();
			boolean needActionContributionItemUpdate = false;
			while (it.hasNext()) {
				IContributionItem item = (IContributionItem) it.next();
				IMenuManager mm = (IMenuManager) map.get(item);
				if (menu.getId() != null && menu.getId().equals(mm.getId()) && item instanceof ActionContributionItem) {
					try {
						IAction action = ((ActionContributionItem) item).getAction();

						if (action instanceof IExtendedEditorActionProxyForDelayLoading) {
						    IExtendedEditorActionProxyForDelayLoading eea = (IExtendedEditorActionProxyForDelayLoading)action;
						    if (eea.isBundleActive() == true && eea.isRealized() == false) {
						        eea.realize();
						        needActionContributionItemUpdate = true;
						    }
						}

						if (activeEditorIsVisible || needActionContributionItemUpdate) {
							if (action instanceof IUpdate) {
								((IUpdate) action).update();
							}
						}

						if (activeEditorIsVisible || needActionContributionItemUpdate) {
							boolean visible = true;
							if (action instanceof IExtendedEditorAction) {
								visible = ((IExtendedEditorAction) action).isVisible();
							}
							item.setVisible(visible);
						}

						if (needActionContributionItemUpdate) {
						    ((ActionContributionItem)item).update();
						}

					}
					catch (Exception e) {
						Logger.logException("updating actions", e); //$NON-NLS-1$
					}
				}
			}
			if (activeEditorIsVisible || needActionContributionItemUpdate) {
			    if (needActionContributionItemUpdate) {
			        // the action is realized so that need to update the menu w/
			        // force set to true
			        menu.update(true);
			    } else {
			        menu.update(false);
			    }
			}
		}

		public void setActiveEditor(IEditorPart editor) {
			activeExtendedEditor = computeExtendedEditor(editor);
			IKeyBindingService svc = (editor != null) ? editor.getEditorSite().getKeyBindingService() : null;
			for (int i = 0; i < cache.size(); i++) {
				Object obj = cache.get(i);
				if (obj instanceof ActionDescriptor) {
					ActionDescriptor ad = (ActionDescriptor) obj;
					try {
						IAction action = ad.getAction();
						if (action instanceof IExtendedEditorAction) {
							((IExtendedEditorAction) action).setActiveExtendedEditor(activeExtendedEditor);
							((IExtendedEditorAction) action).update();
							// update visibility right now so that the menu
							// will show/hide properly
							if (!((IExtendedEditorAction) action).isVisible() && ad.getContributionItem() != null)
								ad.getContributionItem().setVisible(false);
							if (svc != null && action.getActionDefinitionId() != null) {
								svc.registerAction(action);
							}
						}
					}
					catch (Exception e) {
						Logger.logException("setting active editor on actions", e); //$NON-NLS-1$
					}
				}
			}

			if (menuBar != null && editor != null) {
				// Class clz = editor.getClass();
				// while (clz != null) {
				// if (clz.getName().equals(targetID)) {
				// contributeToMenu(menuBar);
				// break;
				// }
				// clz = clz.getSuperclass();
				// }
				if (targetIDs.contains(editor.getEditorSite().getId())) {
					contributeToMenu(menuBar);
				}
			}

			updateToolbarActions();
		}

		public void updateToolbarActions() {
			for (int i = 0; i < cache.size(); i++) {
				Object obj = cache.get(i);
				if (obj instanceof ActionDescriptor) {
					try {
						ActionDescriptor ad = (ActionDescriptor) obj;
						if (ad.getToolbarPath() != null) {
							IAction action = ad.getAction();
							if (action instanceof IUpdate) {
								((IUpdate) action).update();
							}
						}
					}
					catch (Exception e) {
						Logger.logException("updating toolbar actions", e); //$NON-NLS-1$
					}
				}
			}
		}
	}

	public static final String ATT_ID = "id"; //$NON-NLS-1$
	public static final String ATT_LABEL = "label"; //$NON-NLS-1$
	public static final String ATT_NAME = "name"; //$NON-NLS-1$
	public static final String ATT_PATH = "path"; //$NON-NLS-1$

	public static final String ATT_TARGET_ID = "targetID"; //$NON-NLS-1$
	protected final static boolean debugContributeTime = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.ui/extendededitoractionbuilder/contributetime")); //$NON-NLS-1$  //$NON-NLS-2$

	protected final static boolean debugMenu = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.ui/extendededitoractionbuilder/debugmenu")); //$NON-NLS-1$  //$NON-NLS-2$;
	protected final static boolean debugPopup = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.ui/extendededitoractionbuilder/debugpopup")); //$NON-NLS-1$  //$NON-NLS-2$;
	protected final static boolean debugReadTime = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.ui/extendededitoractionbuilder/readtime")); //$NON-NLS-1$  //$NON-NLS-2$
	protected final static boolean debugToolbar = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.wst.sse.ui/extendededitoractionbuilder/debugtoolbar")); //$NON-NLS-1$  //$NON-NLS-2$;

	private static final String EXTENDED_EDITOR = "extendedEditor"; //$NON-NLS-1$

	public static final String PL_EXTENDED_EDITOR_ACTIONS = "extendedEditorActions"; //$NON-NLS-1$

	public static final String PLUGIN_ID = "org.eclipse.wst.sse.ui"; //$NON-NLS-1$
	public static final String TAG_ACTION = "action"; //$NON-NLS-1$

	public static final String TAG_CONTRIBUTION_TYPE = "editorContribution"; //$NON-NLS-1$

	public static final String TAG_MENU = "menu"; //$NON-NLS-1$
	public static final String TAG_POPUPMENU = "popupmenu"; //$NON-NLS-1$
	public static final String TAG_RULERMENU = "rulermenu"; //$NON-NLS-1$
	public static final String TAG_SEPARATOR = "separator"; //$NON-NLS-1$

	protected List readingCache;

	protected String targetContributionTag;
	protected List targetIDs;

	/**
	 * The constructor.
	 */
	public ExtendedEditorActionBuilder() {
		super();
	}

	/**
	 * Creates a menu from the information in the menu configuration element
	 * and adds it into the provided menu manager. If 'appendIfMissing' is
	 * true, and menu path slot is not found, it will be created and menu will
	 * be added into it. Otherwise, add operation will fail.
	 */
	protected void contributeMenu(IConfigurationElement menuElement, IMenuManager mng, boolean appendIfMissing) {
		// Get config data.
		String id = menuElement.getAttribute(ATT_ID);
		String label = menuElement.getAttribute(ATT_LABEL);
		String path = menuElement.getAttribute(ATT_PATH);
		if (label == null) {
			Logger.log(Logger.ERROR, "Invalid Menu Extension (label == null): " + id); //$NON-NLS-1$
			return;
		}

		// Calculate menu path and group.
		String group = null;
		if (path != null) {
			int loc = path.lastIndexOf('/');
			if (loc != -1) {
				group = path.substring(loc + 1);
				path = path.substring(0, loc);
			}
			else {
				// assume that path represents a slot
				// so actual path portion should be null
				group = path;
				path = null;
			}
		}

		// Find parent menu.
		IMenuManager parent = mng;
		if (path != null) {
			parent = mng.findMenuUsingPath(path);
			if (parent == null) {
				// Logger.log("Invalid Menu Extension (Path is invalid): " +
				// id);//$NON-NLS-1$
				return;
			}
			// IMenuManager.findMenuUsingPath() returns invisible menu item if
			// the manager can't find
			// the specified path and create new MenuManager for it.
			// I don't know this is a specification or bug.
			// Anyway, to ensure the menu can be visible, setVisible(true)
			// needs to be called.
			parent.setVisible(true);
		}

		// Find reference group.
		if (group == null)
			group = IWorkbenchActionConstants.MB_ADDITIONS;
		IContributionItem sep = parent.find(group);
		if (sep == null) {
			if (appendIfMissing)
				parent.add(new Separator(group));
			else {
				Logger.log(Logger.ERROR, "Invalid Menu Extension (Group is invalid): " + id); //$NON-NLS-1$
				return;
			}
		}

		// If the menu does not exist create it.
		IMenuManager newMenu = parent.findMenuUsingPath(id);
		if (newMenu == null)
			newMenu = new MenuManager(label, id);

		// Create separators.
		IConfigurationElement[] children = menuElement.getChildren(TAG_SEPARATOR);
		for (int i = 0; i < children.length; i++) {
			contributeSeparator(newMenu, children[i]);
		}

		// Add new menu
		try {
			parent.insertAfter(group, newMenu);
		}
		catch (IllegalArgumentException e) {
			Logger.log(Logger.ERROR, "Invalid Menu Extension (Group is missing): " + id); //$NON-NLS-1$
		}
	}

	/**
	 * Contributes action from action descriptor into the provided menu
	 * manager.
	 */
	protected IMenuManager contributeMenuAction(ActionDescriptor ad, IMenuManager menu, boolean appendIfMissing, boolean popupmenu) {
		if (ad.getContributionItem() == null || ad.getAction() == null)
			return null;

		// Get config data.
		String mpath = popupmenu ? ad.getPopupMenuPath() : ad.getMenuPath();
		String mgroup = popupmenu ? ad.getPopupMenuGroup() : ad.getMenuGroup();
		if (mpath == null && mgroup == null)
			return null;

		// Find parent menu.
		IMenuManager parent = menu;
		if (mpath != null) {
			parent = parent.findMenuUsingPath(mpath);
			if (parent == null) {
				// Logger.log("Invalid Menu Extension (Path is invalid): " +
				// ad.getId()); //$NON-NLS-1$
				return null;
			}
			// IMenuManager.findMenuUsingPath() returns invisible menu item if
			// the manager can't find
			// the specified path and create new MenuManager for it.
			// I don't know this is a specification or bug.
			// Anyway, to ensure the menu can be visible, setVisible(true)
			// needs to be called.
			parent.setVisible(true);
		}

		// First remove existing menu item
		IContributionItem item = parent.find(ad.getId());
		if (item != null) {
			parent.remove(ad.getId());
		}

		// Find reference group.
		if (mgroup == null)
			mgroup = IWorkbenchActionConstants.MB_ADDITIONS;
		IContributionItem sep = parent.find(mgroup);
		if (sep == null) {
			if (appendIfMissing)
				parent.add(sep = new Separator(mgroup));
			else {
				Logger.log(Logger.ERROR, "Invalid Menu Extension (Group is invalid): " + ad.getId()); //$NON-NLS-1$
				return null;
			}
		}

		// Add action.
		try {
			if (popupmenu) {
				// Context menu need a newly created contribution item
				if (sep != null && sep.isGroupMarker())
					parent.appendToGroup(sep.getId(), ad.getAction());
				else
					parent.insertAfter(mgroup, ad.getAction());
			}
			else {
				// Normal menu need to add existing contribution item to
				// remove it from menu listener
				if (sep != null && sep.isGroupMarker())
					parent.appendToGroup(sep.getId(), ad.getContributionItem());
				else
					parent.insertAfter(mgroup, ad.getContributionItem());
			}
		}
		catch (IllegalArgumentException e) {
			Logger.log(Logger.ERROR, "Invalid Menu Extension (Group is missing): " + ad.getId()); //$NON-NLS-1$
			parent = null;
		}

		return parent;
	}

	/**
	 * Creates a named menu separator from the information in the
	 * configuration element. If the separator already exists do not create a
	 * second.
	 */
	protected boolean contributeSeparator(IMenuManager menu, IConfigurationElement element) {
		String id = element.getAttribute(ATT_NAME);
		if (id == null || id.length() <= 0)
			return false;
		IContributionItem sep = menu.find(id);
		if (sep != null)
			return false;
		menu.add(new Separator(id));
		return true;
	}

	/**
	 * Contributes action from the action descriptor into the provided tool
	 * bar manager.
	 */
	protected boolean contributeToolbarAction(ActionDescriptor ad, IToolBarManager toolbar, boolean appendIfMissing) {
		if (ad.getContributionItem() == null || ad.getAction() == null)
			return false;

		// Get config data.
		String tpath = ad.getToolbarPath();
		String tgroup = ad.getToolbarGroup();
		if (tpath == null && tgroup == null)
			return false;

		// First remove existing toolbar item
		IContributionItem item = toolbar.find(ad.getId());
		if (item != null) {
			toolbar.remove(ad.getId());
		}

		// Find reference group.
		if (tgroup == null)
			tgroup = IWorkbenchActionConstants.MB_ADDITIONS;
		IContributionItem sep = toolbar.find(tgroup);
		if (sep == null) {
			if (appendIfMissing)
				toolbar.add(new Separator(tgroup));
			else {
				Logger.log(Logger.ERROR, "Invalid Toolbar Extension (Group is invalid): " + ad.getId()); //$NON-NLS-1$
				return false;
			}
		}

		// Add action to tool bar.
		try {
			if (sep != null && sep.isGroupMarker())
				toolbar.appendToGroup(sep.getId(), ad.getAction());
			else
				toolbar.insertAfter(tgroup, ad.getAction());
		}
		catch (IllegalArgumentException e) {
			Logger.log(Logger.ERROR, "Invalid Toolbar Extension (Group is missing): " + ad.getId()); //$NON-NLS-1$
			return false;
		}
		return true;
	}

	/**
	 * This factory method returns a new ActionDescriptor for the
	 * configuration element. It should be implemented by subclasses.
	 */
	protected ActionDescriptor createActionDescriptor(IConfigurationElement element) {
		ActionDescriptor ad = null;
		try {
			ad = new ActionDescriptor(element);
			// these cases like "class not found" are handled
			// at lower level, so no action if formed. In that
			// case, we also don't want to form an action descriptor.
			if ((ad != null) && (ad.getAction() == null)) {
				ad = null;
			}
		}
		catch (Exception e) {
			Logger.traceException(EXTENDED_EDITOR, e);
			ad = null;
		}
		return ad;
	}

	/**
	 * Returns the name of the part ID attribute that is expected in the
	 * target extension.
	 */
	protected String getTargetID(IConfigurationElement element) {
		String value = element.getAttribute(ATT_TARGET_ID);
		return value != null ? value : "???"; //$NON-NLS-1$
	}

	/**
	 * Reads editor contributor if specified directly in the 'editor'
	 * extension point, and all external contributions for this editor's ID
	 * registered in 'editorActions' extension point.
	 */
	public IExtendedContributor readActionExtensions(String editorId) {
		return readActionExtensions(new String[]{editorId});
	}

	/**
	 * Reads editor contributor if specified directly in the 'editor'
	 * extension point, and all external contributions for this editor's ID
	 * registered in 'editorActions' extension point.
	 */
	public IExtendedContributor readActionExtensions(String[] ids) {
		long time0 = System.currentTimeMillis();
		ExtendedContributor ext = null;
		readContributions(ids, TAG_CONTRIBUTION_TYPE, PL_EXTENDED_EDITOR_ACTIONS);
		if (debugReadTime) {
			String idlist = ""; //$NON-NLS-1$
			if (ids.length > 0) {
				for (int i = 0; i < ids.length; i++) {
					idlist += ids[i];
					if (i < ids.length - 1)
						idlist += ","; //$NON-NLS-1$
				}
			}
			System.out.println(getClass().getName() + "#readActionExtensions(" + idlist + "): read in " + (System.currentTimeMillis() - time0) + "ms [" + (readingCache != null ? readingCache.size() : 0) + " contributions]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
		}
		if (readingCache != null) {
			ext = new ExtendedContributor(readingCache);
			readingCache = null;
		}
		return ext;
	}

	/**
	 * Reads the contributions from the registry for the provided workbench
	 * part and the provided extension point IDs.
	 */
	protected void readContributions(String[] ids, String tag, String extensionPoint) {
		readingCache = null;
		targetIDs = Arrays.asList(ids);
		targetContributionTag = tag;
		IExtensionRegistry registry = Platform.getExtensionRegistry();
		readRegistry(registry, PLUGIN_ID, extensionPoint);
	}

	/**
	 * Implements abstract method to handle the provided XML element in the
	 * registry.
	 */
	protected boolean readElement(IConfigurationElement element) {
		String tag = element.getName();
		if (tag.equals(targetContributionTag)) {
			String id = getTargetID(element);
			if (id == null || !targetIDs.contains(id)) {
				// This is not of interest to us - don't go deeper
				return true;
			}
		}
		else if (tag.equals(TAG_MENU)) {
			if (readingCache == null)
				readingCache = new ArrayList();
			readingCache.add(element);
			return true; // just cache the element - don't go into it
		}
		else if (tag.equals(TAG_POPUPMENU)) {
			if (readingCache == null)
				readingCache = new ArrayList();
			readingCache.add(element);
			return true; // just cache the element - don't go into it
		}
		else if (tag.equals(TAG_RULERMENU)) {
			if (readingCache == null)
				readingCache = new ArrayList();
			readingCache.add(element);
			return true; // just cache the element - don't go into it
		}
		else if (tag.equals(TAG_ACTION)) {
			if (readingCache == null)
				readingCache = new ArrayList();
			ActionDescriptor ad = createActionDescriptor(element);
			if (ad != null)
				readingCache.add(ad);
			return true; // just cache the action - don't go into
		}
		else {
			return false;
		}

		readElementChildren(element);
		return true;
	}
}
