/*******************************************************************************
* Copyright (c) 2012 VMware, Inc.
* 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:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.config.ui.editors;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
import org.springframework.ide.eclipse.beans.ui.editor.namespaces.NamespaceUtils;
import org.springframework.ide.eclipse.config.core.ConfigCoreUtils;
import org.springframework.ide.eclipse.config.core.contentassist.SpringConfigContentAssistProcessor;
import org.springframework.ide.eclipse.config.core.extensions.PageAdaptersExtensionPointConstants;
import org.springframework.ide.eclipse.config.ui.actions.CollapseNodeAction;
import org.springframework.ide.eclipse.config.ui.actions.DeleteNodeAction;
import org.springframework.ide.eclipse.config.ui.actions.ExpandNodeAction;
import org.springframework.ide.eclipse.config.ui.actions.InsertNodeAction;
import org.springframework.ide.eclipse.config.ui.actions.LowerNodeAction;
import org.springframework.ide.eclipse.config.ui.actions.RaiseNodeAction;
import org.springframework.ide.eclipse.wizard.ui.BeanWizardDialog;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
/**
* This class is an extension to {@link AbstractConfigMasterPart} that is suited
* to displaying a structured overview of elements in a Spring configuration
* file belonging to a specific Spring namespace.
* @author Leo Dos Santos
* @author Terry Denney
* @author Christian Dupuis
* @see AbstractConfigMasterPart
* @since 2.0.0
*/
@SuppressWarnings("restriction")
public abstract class AbstractNamespaceMasterPart extends AbstractConfigMasterPart {
private class SelectionChangedListener implements ISelectionChangedListener {
public void selectionChanged(SelectionChangedEvent event) {
if (upButton != null && downButton != null) {
ISelection selection = treeViewer.getSelection();
int count = treeViewer.getTree().getSelectionCount();
if (selection.isEmpty() || count > 1) {
upButton.setEnabled(false);
downButton.setEnabled(false);
}
else {
upButton.setEnabled(true);
downButton.setEnabled(true);
}
}
}
}
private TreeViewer treeViewer;
private SelectionChangedListener selectionListener;
private Button newBeanButton;
private Button upButton;
private Button downButton;
private static String SCHEMA_URI_PREFIX = "http://www.springframework.org/schema/"; //$NON-NLS-1$
private final SpringConfigContentAssistProcessor xmlProcessor;
/**
* Constructs a master part with a reference to its container page and its
* parent composite.
*
* @param page the hosting form page
* @param parent the parent composite
*/
public AbstractNamespaceMasterPart(AbstractConfigFormPage page, Composite parent) {
super(page, parent);
xmlProcessor = page.getXmlProcessor();
}
@Override
protected void createButtons(Composite client) {
GridData data = new GridData(GridData.FILL_HORIZONTAL);
data.widthHint = 100;
newBeanButton = toolkit.createButton(client,
Messages.getString("AbstractNamespaceMasterPart.NEW_BEAN_BUTTON"), SWT.FLAT); //$NON-NLS-1$
newBeanButton.setLayoutData(data);
newBeanButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
IFile sourceFile = getConfigEditor().getResourceFile();
Shell shell = getFormPage().getSite().getShell();
if (shell != null && !shell.isDisposed()) {
BeanWizardDialog dialog = BeanWizardDialog.createBeanWizardDialog(shell, sourceFile, false);
dialog.create();
dialog.setBlockOnOpen(true);
if (dialog.open() == Window.OK) {
IDOMElement element = dialog.getNewBean();
getViewer().setSelection(new StructuredSelection(element));
}
}
}
});
upButton = toolkit.createButton(client,
Messages.getString("AbstractNamespaceMasterPart.MOVE_UP_BUTTON"), SWT.FLAT); //$NON-NLS-1$
upButton.setLayoutData(data);
upButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
StructuredTextViewer textView = getConfigEditor().getTextViewer();
Action action = new RaiseNodeAction(treeViewer, xmlProcessor, textView);
action.run();
}
});
upButton.setEnabled(false);
downButton = toolkit.createButton(client,
Messages.getString("AbstractNamespaceMasterPart.MOVE_DOWN_BUTTON"), SWT.FLAT); //$NON-NLS-1$
downButton.setLayoutData(data);
downButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
StructuredTextViewer textView = getConfigEditor().getTextViewer();
Action action = new LowerNodeAction(treeViewer, xmlProcessor, textView);
action.run();
}
});
downButton.setEnabled(false);
}
/**
* This method is called automatically when a context menu is invoked on the
* master viewer when the viewer is empty. Clients may override to add new
* actions to the menu.
*
* @param manager the menu manager on the master viewer
* @param doc document object model of the XML source file
*/
protected abstract void createEmptyDocumentActions(IMenuManager manager, IDOMDocument doc);
/**
* This method is called automatically when a context menu is invoked on the
* master viewer, and adds new actions to create child nodes and delete the
* selected node. Clients may extend to add additional actions to the menu.
*
* @param manager the menu manager on the master viewer
* @param parent the selected XML node
*/
protected void createNodeInsertActions(IMenuManager manager, IDOMElement parent) {
StructuredTextViewer textView = getConfigEditor().getTextViewer();
Map<String, List<String>> childMap = getChildNames(parent);
if (childMap.keySet().size() > 1) {
for (String prefix : childMap.keySet()) {
MenuManager subManager;
String label;
if (prefix.trim().equals("")) { //$NON-NLS-1$
String uri = ConfigCoreUtils.getDefaultNamespaceUri(getConfigEditor().getDomDocument());
if (uri == null) {
uri = NamespaceUtils.DEFAULT_NAMESPACE_URI;
}
int pos = uri.indexOf(SCHEMA_URI_PREFIX);
if (pos > -1) {
label = uri.substring(pos + SCHEMA_URI_PREFIX.length(), uri.length());
}
else {
label = Messages.getString("AbstractNamespaceMasterPart.DEFAULT_NAMESPACE_SUBMENU"); //$NON-NLS-1$
}
subManager = new MenuManager(label);
}
else {
subManager = new MenuManager(prefix);
}
for (String childName : childMap.get(prefix)) {
subManager.add(new InsertNodeAction(treeViewer, xmlProcessor, textView, childName));
}
manager.add(subManager);
}
}
else {
for (String prefix : childMap.keySet()) {
for (String childName : childMap.get(prefix)) {
manager.add(new InsertNodeAction(treeViewer, xmlProcessor, textView, childName));
}
}
}
}
@Override
protected ColumnViewer createViewer(Composite client) {
FilteredTree filter = new FilteredTree(client, SWT.MULTI | SWT.BORDER, new PatternFilter(), true);
treeViewer = filter.getViewer();
return treeViewer;
}
/**
* This method is called automatically when the master part is created. This
* implementation returns a {@link SpringConfigContentProvider}, but clients
* may override to return their own content provider.
*
* @return content provider for the master viewer
*/
@Override
protected SpringConfigContentProvider createViewerContentProvider() {
return new SpringConfigContentProvider(getFormPage());
}
/**
* This method is called automatically when the master part is created. This
* implementation returns a {@link SpringConfigLabelProvider}, but clients
* may override to return their own label provider.
*
* @return label provider for the master viewer
*/
@Override
protected AbstractConfigLabelProvider createViewerLabelProvider() {
return new SpringConfigLabelProvider();
}
@Override
public void dispose() {
if (treeViewer != null && selectionListener != null) {
treeViewer.removeSelectionChangedListener(selectionListener);
}
super.dispose();
}
@Override
protected void fillContextMenu(IMenuManager manager) {
IStructuredSelection selection = (IStructuredSelection) getViewer().getSelection();
StructuredTextViewer textView = getConfigEditor().getTextViewer();
if (!selection.isEmpty()) {
Object obj = selection.getFirstElement();
if (obj != null && obj instanceof IDOMElement) {
IDOMElement node = (IDOMElement) obj;
createNodeInsertActions(manager, node);
manager.add(new Separator());
manager.add(new DeleteNodeAction(textView, node));
}
}
else {
IDOMDocument doc = getConfigEditor().getDomDocument();
if (doc != null) {
createEmptyDocumentActions(manager, doc);
}
}
}
/**
* Returns a map of namespace prefixes to node names that can be added as
* children to the given parent. Clients may override if necessary.
*
* @see SpringConfigContentProvider#getChildNames(String)
* @param parent the parent element
* @return map of namespace prefixes to child names for the given parent
*/
protected Map<String, List<String>> getChildNames(IDOMElement parent) {
SpringConfigContentAssistProcessor proc = getFormPage().getXmlProcessor();
Node grandParent = parent.getParentNode();
Map<String, List<String>> childMap = new TreeMap<String, List<String>>();
if (proc != null) {
List<String> list = proc.getChildNames(parent);
String uri = getFormPage().getNamespaceUri();
for (String name : list) {
String prefix = ""; //$NON-NLS-1$
List<String> names;
int pos = name.indexOf(':');
if (pos > -1) {
prefix = name.substring(0, pos);
}
if (childMap.containsKey(prefix)) {
names = childMap.get(prefix);
}
else {
names = new ArrayList<String>();
}
if (grandParent instanceof Document && uri != null) {
String prefixForUri = getFormPage().getPrefixForNamespaceUri();
if (prefix.equals(prefixForUri) || isAdapterNamespacePrefix(prefix)) {
names.add(name);
childMap.put(prefix, names);
}
}
else {
names.add(name);
childMap.put(prefix, names);
}
}
}
return childMap;
}
/**
* This method is called automatically when the master part is created. This
* implementation returns a generic description, but clients may override to
* return their own description.
*
* @return master part description
*/
@Override
protected String getSectionDescription() {
return Messages.getString("AbstractNamespaceMasterPart.SECTION_DESCRIPTION"); //$NON-NLS-1$
}
private boolean isAdapterNamespacePrefix(String prefix) {
if (prefix != null) {
for (IConfigurationElement config : getFormPage().getAdapterDefinitions()) {
String adapterPrefix = ConfigCoreUtils.getPrefixForNamespaceUri(getConfigEditor().getDomDocument(),
config.getAttribute(PageAdaptersExtensionPointConstants.ATTR_NAMESPACE_URI));
if (adapterPrefix != null && adapterPrefix.equals(prefix)) {
return true;
}
}
}
return false;
}
@Override
protected void postCreateContents() {
if (treeViewer != null) {
selectionListener = new SelectionChangedListener();
treeViewer.addSelectionChangedListener(selectionListener);
ToolBarManager manager = getToolBarManager();
manager.add(new CollapseNodeAction(treeViewer, xmlProcessor));
manager.add(new ExpandNodeAction(treeViewer, xmlProcessor));
treeViewer.expandToLevel(2);
}
}
@Override
public void refresh() {
if (newBeanButton != null) {
IDOMDocument document = getConfigEditor().getDomDocument();
if (document == null || document.getDocumentElement() == null) {
newBeanButton.setEnabled(false);
}
else {
newBeanButton.setEnabled(true);
}
}
super.refresh();
}
}