/*******************************************************************************
* Copyright (c) 2004, 2014 Spring IDE Developers
* 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:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.beans.ui.properties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.ParseException;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalListener;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.ide.eclipse.beans.core.BeansCorePlugin;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansConfig;
import org.springframework.ide.eclipse.beans.core.internal.model.BeansConfigFactory;
import org.springframework.ide.eclipse.beans.core.model.IBeansComponent;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig.Type;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfigSet;
import org.springframework.ide.eclipse.beans.core.model.IBeansModel;
import org.springframework.ide.eclipse.beans.core.model.IBeansProject;
import org.springframework.ide.eclipse.beans.core.model.IProfileAwareBeansComponent;
import org.springframework.ide.eclipse.beans.ui.BeansUIImages;
import org.springframework.ide.eclipse.beans.ui.BeansUIPlugin;
import org.springframework.ide.eclipse.beans.ui.properties.model.PropertiesConfigSet;
import org.springframework.ide.eclipse.beans.ui.properties.model.PropertiesModelLabelProvider;
import org.springframework.ide.eclipse.beans.ui.properties.model.PropertiesProject;
import org.springframework.ide.eclipse.core.model.ModelUtils;
import org.springsource.ide.eclipse.commons.ui.SpringUIUtils;
import org.springframework.util.StringUtils;
/**
* Dialog for creating a beans config set.
* @author Torsten Juergeleit
* @author Christian Dupuis
* @author Martin Lippert
* @author Terry Denney
*/
public class ConfigSetDialog extends Dialog {
private static final String PREFIX = "ConfigSetDialog.";
private static final String TITLE_NEW = PREFIX + "title.new";
private static final String TITLE_EDIT = PREFIX + "title.edit";
private static final String ERROR_INVALID_NAME = PREFIX
+ "error.invalidName";
private static final String ERROR_USED_NAME = PREFIX + "error.usedName";
private static final String NAME_LABEL = PREFIX + "name.label";
private static final String PROFILE_LABEL = PREFIX + "profiles.label";
private static final String OVERRIDE_LABEL = PREFIX + "override.label";
private static final String INCOMPLETE_LABEL = PREFIX + "incomplete.label";
private static final String VIEWER_LABEL = PREFIX + "viewer.label";
private static final String SELECT_ALL_LABEL = PREFIX + "select.all.label";
private static final String DESELECT_ALL_LABEL = PREFIX + "deselect.all.label";
private static final int LIST_VIEWER_WIDTH = 400;
private static final int LIST_VIEWER_HEIGHT = 250;
private CLabel messageLabel;
private Text nameText;
private Text profilesText;
private Button overrideButton;
private Button incompleteButton;
private CheckboxTableViewer configsViewer;
private Label errorLabel;
private Button okButton;
private PropertiesProject project;
private PropertiesConfigSet configSet;
private String title;
private enum Mode { NEW, EDIT };
private Mode mode;
public ConfigSetDialog(Shell parent, PropertiesProject project,
String configSetName, IBeansConfigSet.Type type) {
super(parent);
this.project = project;
if (configSetName == null) {
configSet = new PropertiesConfigSet(project, (String) null, type);
title = BeansUIPlugin.getResourceString(TITLE_NEW);
mode = Mode.NEW;
} else {
configSet = (PropertiesConfigSet) project.getConfigSet(configSetName);
title = BeansUIPlugin.getResourceString(TITLE_EDIT);
mode = Mode.EDIT;
}
}
@Override
protected void configureShell(Shell shell) {
super.configureShell(shell);
if (title != null) {
shell.setText(title);
}
}
@Override
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite) super.createDialogArea(parent);
// Create group composite for options
Composite optionsGroup = new Composite(composite, SWT.NULL);
optionsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
GridLayout layout = new GridLayout();
optionsGroup.setLayout(layout);
messageLabel = new CLabel(optionsGroup, SWT.NONE);
messageLabel.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
// Create labeled name text field
nameText = SpringUIUtils.createTextField(optionsGroup, BeansUIPlugin
.getResourceString(NAME_LABEL), 0, 0, 50);
nameText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
validateName();
}
});
// Create labeled profiles text field
profilesText = SpringUIUtils.createTextField(optionsGroup, BeansUIPlugin
.getResourceString(PROFILE_LABEL), 0, 0, 50);
profilesText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
validateProfiles();
}
});
profilesText.setText(StringUtils.collectionToDelimitedString(configSet.getProfiles(), ", "));
createProfilesContentAssist();
Label options = new Label(optionsGroup, SWT.WRAP);
options.setText("Options:");
options.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// Create labeled checkboxes
overrideButton = SpringUIUtils.createCheckBox(optionsGroup,
BeansUIPlugin.getResourceString(OVERRIDE_LABEL));
overrideButton.setSelection(configSet
.isAllowBeanDefinitionOverriding());
incompleteButton = SpringUIUtils.createCheckBox(optionsGroup,
BeansUIPlugin.getResourceString(INCOMPLETE_LABEL));
incompleteButton.setSelection(configSet.isIncomplete());
// Create config set list viewer
Label viewerLabel = new Label(composite, SWT.NONE);
GridData gd = new GridData(GridData.GRAB_HORIZONTAL
| GridData.HORIZONTAL_ALIGN_FILL);
viewerLabel.setLayoutData(gd);
viewerLabel.setText(BeansUIPlugin.getResourceString(VIEWER_LABEL));
configsViewer = CheckboxTableViewer.newCheckList(composite, SWT.BORDER);
gd = new GridData(GridData.FILL_BOTH);
gd.widthHint = LIST_VIEWER_WIDTH;
gd.heightHint = LIST_VIEWER_HEIGHT;
configsViewer.getTable().setLayoutData(gd);
configsViewer.setContentProvider(new ConfigFilesContentProvider(
createConfigList()));
configsViewer.setLabelProvider(new PropertiesModelLabelProvider());
configsViewer.setSorter(new ConfigFilesSorter());
configsViewer.setInput(this); // activate content provider
configsViewer.setCheckedElements(configSet.getConfigs().toArray());
configsViewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
validateProfiles();
}
});
// Create select all and deselect all buttons
Composite buttonsGroup = new Composite(composite, SWT.NULL);
buttonsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
layout = new GridLayout();
layout.numColumns = 2;
layout.marginTop = 0;
layout.marginBottom = 10;
buttonsGroup.setLayout(layout);
buttonsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
SpringUIUtils.createButton(buttonsGroup, BeansUIPlugin
.getResourceString(SELECT_ALL_LABEL), new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
configsViewer.setAllChecked(true);
}
});
SpringUIUtils.createButton(buttonsGroup, BeansUIPlugin
.getResourceString(DESELECT_ALL_LABEL), new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
configsViewer.setAllChecked(false);
}
});
// Create error label
errorLabel = new Label(composite, SWT.NONE);
errorLabel.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
| GridData.HORIZONTAL_ALIGN_FILL));
errorLabel.setForeground(JFaceColors.getErrorText(parent.getDisplay()));
errorLabel.setBackground(JFaceColors.getErrorBackground(parent
.getDisplay()));
applyDialogFont(composite);
return composite;
}
private Set<String> getDefinedProfiles(IBeansComponent component, String filter, Set<String> existingProfiles) {
Set<String> profiles = new HashSet<String>();
if (component instanceof IProfileAwareBeansComponent) {
Set<String> currentProfiles = ((IProfileAwareBeansComponent) component).getProfiles();
for(String profile: currentProfiles) {
if (profile.startsWith(filter) && !existingProfiles.contains(profile)) {
profiles.add(profile);
}
}
}
Set<IBeansComponent> children = component.getComponents();
for(IBeansComponent child: children) {
profiles.addAll(getDefinedProfiles(child, filter, existingProfiles));
}
return profiles;
}
private class ProfileContentProposal implements IContentProposal {
private final String profile;
private final int offset;
private final int cursorPosition;
public ProfileContentProposal(String profile, int offset, int cursorPosition) {
this.profile = profile;
this.offset = offset;
this.cursorPosition = cursorPosition;
}
public String getLabel() {
return profile;
}
public String getDescription() {
Set<IBeansConfig> configs = getDefiningConfigForProfile(profile);
Set<String> configNames = new HashSet<String>();
for(IBeansConfig config: configs) {
configNames.add(config.getElementName());
}
return "Profile \"" + profile + "\" defined in " + StringUtils.collectionToDelimitedString(configNames, ", ");
}
public int getCursorPosition() {
return offset + profile.length();
}
public String getContent() {
return profile.substring(cursorPosition - offset, profile.length());
}
public String getProfile() {
return profile;
}
}
private void createProfilesContentAssist() {
IContentProposalProvider proposalProvider = new IContentProposalProvider() {
public IContentProposal[] getProposals(String contents, final int position) {
String prefix = contents.substring(0, position);
String filter = prefix;
Set<String> existingProfiles = new HashSet<String>();
int separatorIndex = -1;
while(filter.contains(",")) {
separatorIndex = prefix.indexOf(",");
if (separatorIndex >= 0) {
existingProfiles.add(prefix.substring(0, separatorIndex));
filter = prefix.substring(separatorIndex + 1);
while(filter.startsWith(" ")) {
separatorIndex++;
filter = filter.substring(1);
}
}
}
Set<IBeansConfig> configs = project.getConfigs();
Set<String> profiles = new HashSet<String>();
for(IBeansConfig config: configs) {
Set<IBeansComponent> components = config.getComponents();
for(IBeansComponent component: components) {
profiles.addAll(getDefinedProfiles(component, filter, existingProfiles));
}
}
List<IContentProposal> proposals = new ArrayList<IContentProposal>();
for(final String profile: profiles) {
proposals.add(new ProfileContentProposal(profile, separatorIndex + 1, position));
}
Collections.sort(proposals, new Comparator<IContentProposal>() {
public int compare(IContentProposal o1, IContentProposal o2) {
return o1.getLabel().compareTo(o2.getLabel());
}
});
return proposals.toArray(new IContentProposal[proposals.size()]);
}
};
try {
KeyStroke keyStroke = KeyStroke.getInstance("Ctrl+Space");
ContentProposalAdapter contentProposalAdapter = new ContentProposalAdapter(profilesText, new TextContentAdapter(), proposalProvider, keyStroke, null);
contentProposalAdapter.addContentProposalListener(new IContentProposalListener() {
public void proposalAccepted(IContentProposal proposal) {
if (proposal instanceof ProfileContentProposal) {
ProfileContentProposal profileProposal = (ProfileContentProposal) proposal;
Set<IBeansConfig> configs = getDefiningConfigForProfile(profileProposal.getProfile());
for(IBeansConfig config: configs) {
if (! configsViewer.getChecked(config)) {
configsViewer.setChecked(config, true);
}
}
validateProfiles();
}
}
});
} catch (ParseException e) {
}
}
@Override
protected void createButtonsForButtonBar(Composite parent) {
// Create OK and Cancel buttons by default
okButton = createButton(parent, IDialogConstants.OK_ID,
IDialogConstants.OK_LABEL, true);
createButton(parent, IDialogConstants.CANCEL_ID,
IDialogConstants.CANCEL_LABEL, false);
// Do this here because setting the text will set enablement on the
// ok button
nameText.setFocus();
String name = configSet.getElementName();
if (name != null && name.trim().length() != 0) {
nameText.setText(name);
okButton.setEnabled(true);
} else {
okButton.setEnabled(false);
}
}
@Override
protected void buttonPressed(int buttonId) {
if (buttonId == IDialogConstants.OK_ID) {
// Remove old config set from project
if (mode == Mode.EDIT) {
project.removeConfigSet(configSet.getElementName());
}
// Update config set
configSet.setElementName(nameText.getText());
configSet.setAllowBeanDefinitionOverriding(overrideButton.getSelection());
configSet.setIncomplete(incompleteButton.getSelection());
Set<String> profiles = new LinkedHashSet<String>();
if (profilesText.getText().length() > 0) {
String[] profilesSpec = StringUtils.tokenizeToStringArray(profilesText.getText(), BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
for (String profile : profilesSpec) {
profiles.add(profile);
}
}
configSet.setProfiles(profiles);
// Before removing all configs from this config set keep a copy of
// the original list of configs in the config set
Set<IBeansConfig> oldConfigs = new LinkedHashSet<IBeansConfig>(
configSet.getConfigs());
configSet.removeAllConfigs();
// At first add the originally and still selected configs to the
// config set to preserve their order
List newConfigs = Arrays.asList(configsViewer.getCheckedElements());
for (IBeansConfig config : oldConfigs) {
if (newConfigs.contains(config)) {
configSet.addConfig(config.getElementName());
}
}
// Finally add the newly selected configs to the config set
for (Object newConfig : newConfigs) {
IBeansConfig config = (IBeansConfig) newConfig;
String configName = config.getElementName();
if (!configSet.hasConfig(configName)) {
configSet.addConfig(configName);
}
}
// Read updated or newly created config set
project.addConfigSet(configSet);
}
super.buttonPressed(buttonId);
}
private List<IBeansConfig> createConfigList() {
// Create new list with all config files from this config set
List<IBeansConfig> configs = new ArrayList<IBeansConfig>(configSet.getConfigs());
// Add additional configs from the selected and referenced projects
addConfigsFromReferencedProjects(project, configs, new HashSet<IProject>(), false);
return configs;
}
private void addConfigsFromReferencedProjects(IBeansProject beansProject, List<IBeansConfig> configs, Set<IProject> projects, boolean addProjectPath) {
if (beansProject == null || projects.contains(beansProject.getProject())) {
return;
}
projects.add(beansProject.getProject());
IBeansModel model = BeansCorePlugin.getModel();
try {
for (IBeansConfig config : beansProject.getConfigs()) {
String projectPath = ModelUtils.getResourcePath(config.getElementParent());
if (projectPath != null) {
// Create the full qualified path of the config
// (with support for configs stored in JAR files)
// as long as its not the initiating project
String name = addProjectPath && (config instanceof BeansConfig) ? projectPath + "/" + config.getElementName() : config.getElementName();
if (!configSet.hasConfig(name)) {
configs.add(BeansConfigFactory.create(beansProject, name, Type.MANUAL, false));
}
}
}
// Recursively add configurations to project
for (IProject proj : beansProject.getProject().getProject().getReferencedProjects()) {
IBeansProject referencedProj = model.getProject(proj);
addConfigsFromReferencedProjects(referencedProj, configs, projects, true);
}
} catch (CoreException e) {
// We can't do anything here
}
}
private Set<IBeansConfig> getDefiningConfigForProfile(String profile) {
Set<IBeansConfig> configs = project.getConfigs();
Set<IBeansConfig> definingConfigs = new HashSet<IBeansConfig>();
for (IBeansConfig config : configs) {
Set<IBeansComponent> components = config.getComponents();
for(IBeansComponent component: components) {
Set<String> existingProfiles = Collections.emptySet();
if (getDefinedProfiles(component, "", existingProfiles).contains(profile)) {
definingConfigs.add(config);
break;
}
}
}
return definingConfigs;
}
private void validateProfiles() {
if (configsViewer != null) {
String[] profiles = StringUtils.tokenizeToStringArray(profilesText.getText(), BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
List configs = Arrays.asList(configsViewer.getCheckedElements());
Set<String> unselectedProfiles = new HashSet<String>();
for(String profile: profiles) {
boolean configSelected = false;
for (Object obj : configs) {
IBeansConfig config = (IBeansConfig) obj;
Set<IBeansComponent> components = config.getComponents();
for(IBeansComponent component: components) {
Set<String> existingProfiles = Collections.emptySet();
if (getDefinedProfiles(component, "", existingProfiles).contains(profile)) {
configSelected = true;
}
}
}
if (!configSelected) {
unselectedProfiles.add(profile);
}
}
if (unselectedProfiles.size() > 0) {
messageLabel.setImage(BeansUIImages.getImage(BeansUIImages.IMG_OBJS_WARNING));
messageLabel.setText("Profiles selected (" + StringUtils.arrayToDelimitedString(unselectedProfiles.toArray(), ", ") + ") are not defined in selected config files.");
return;
}
for(String profile: profiles) {
Set<IBeansConfig> definingConfigs = getDefiningConfigForProfile(profile);
for(IBeansConfig definingConfig: definingConfigs) {
if (! configs.contains(definingConfig)) {
messageLabel.setImage(BeansUIImages.getImage(BeansUIImages.IMG_OBJS_WARNING));
messageLabel.setText("Selected config files do not completely define profiles selected.");
return;
}
}
}
}
messageLabel.setImage(null);
if (mode == Mode.NEW) {
messageLabel.setText("Create a new config set.");
} else {
messageLabel.setText("Edit config set.");
}
}
private void validateName() {
boolean isEnabled = false;
String name = nameText.getText();
if (name == null || name.trim().length() == 0) {
errorLabel.setText(BeansUIPlugin
.getResourceString(ERROR_INVALID_NAME));
} else if (mode == Mode.NEW
|| !name.equals(configSet.getElementName())) {
if (project.hasConfigSet(name)) {
errorLabel.setText(BeansUIPlugin
.getResourceString(ERROR_USED_NAME));
} else {
errorLabel.setText("");
isEnabled = true;
}
} else {
errorLabel.setText("");
isEnabled = true;
}
okButton.setEnabled(isEnabled);
errorLabel.getParent().update();
}
private static class ConfigFilesContentProvider implements
IStructuredContentProvider {
private List<IBeansConfig> configs;
public ConfigFilesContentProvider(List<IBeansConfig> configs) {
this.configs = configs;
}
public Object[] getElements(Object obj) {
return configs.toArray();
}
public void inputChanged(Viewer viewer, Object oldInput,
Object newInput) {
}
public void dispose() {
}
}
private static class ConfigFilesSorter extends ViewerSorter {
private enum Category {
SUB_DIR, ROOT_DIR, OTHER
};
@Override
public int category(Object element) {
if (element instanceof IBeansConfig) {
if (((IBeansConfig) element).getElementName()
.indexOf('/') == -1) {
return Category.ROOT_DIR.ordinal();
}
return Category.SUB_DIR.ordinal();
}
return Category.OTHER.ordinal();
}
}
}