blob: 8ce19712db73feb632d7be6a9d44fd4c3f312297 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2019 EclipseSource Muenchen GmbH 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:
* lucas - initial API and implementation
* Christian W. Damus - bugs 529138, 543461, 546974
******************************************************************************/
package org.eclipse.emfforms.internal.core.services.datatemplate;
import static org.osgi.service.component.annotations.ReferenceCardinality.MULTIPLE;
import static org.osgi.service.component.annotations.ReferencePolicy.DYNAMIC;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecp.common.spi.EMFUtils;
import org.eclipse.emf.ecp.ui.view.swt.reference.CreateNewModelElementStrategy;
import org.eclipse.emf.ecp.ui.view.swt.reference.CreateNewModelElementStrategy.Provider;
import org.eclipse.emf.ecp.ui.view.swt.reference.EClassSelectionStrategy;
import org.eclipse.emf.ecp.ui.view.swt.reference.ReferenceServiceCustomizationVendor;
import org.eclipse.emf.ecp.ui.view.swt.reference.ReferenceStrategyUtil;
import org.eclipse.emfforms.bazaar.Bazaar;
import org.eclipse.emfforms.bazaar.BazaarContext;
import org.eclipse.emfforms.bazaar.Create;
import org.eclipse.emfforms.common.Optional;
import org.eclipse.emfforms.core.services.datatemplate.TemplateFilterService;
import org.eclipse.emfforms.core.services.datatemplate.TemplateProvider;
import org.eclipse.emfforms.datatemplate.Template;
import org.eclipse.emfforms.spi.bazaar.BazaarUtil;
import org.eclipse.emfforms.spi.localization.EMFFormsLocalizationService;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.widgets.Display;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
/**
* Provides a strategy to the {@link org.eclipse.emf.ecp.ui.view.swt.DefaultReferenceService DefaultReferenceService}
* that allows creating new model elements based on a template with pre-defined values.
*
* @author Lucas Koehler
*
*/
// Ranking as it was for the TemplateReferenceService
@Component(name = "TemplateCreateNewModelElementStrategyProvider", property = "service.ranking:Integer=10")
public class TemplateCreateNewModelElementStrategyProvider
extends ReferenceServiceCustomizationVendor<CreateNewModelElementStrategy> implements Provider {
private final Set<TemplateProvider> templateProviders = new LinkedHashSet<TemplateProvider>();
private EMFFormsLocalizationService localizationService;
private final Bazaar<EClassSelectionStrategy> eclassSelectionStrategyBazaar = BazaarUtil.createBazaar(
EClassSelectionStrategy.NULL);
private final Bazaar<TemplateFilterService> templateFilterBazaar = BazaarUtil.createBazaar(
TemplateFilterService.NULL);
private ComponentContext context;
/**
* Activates me.
*
* @param context my component context
*/
@Activate
void activate(ComponentContext context) {
this.context = context;
}
/**
* Deactivates me.
*/
@Deactivate
void deactivate() {
context = null;
}
/**
* Add an {@code EClass} selection strategy provider.
*
* @param provider the provider to add
*/
@Reference(cardinality = MULTIPLE, policy = DYNAMIC)
public void addEClassSelectionStrategyProvider(EClassSelectionStrategy.Provider provider) {
eclassSelectionStrategyBazaar.addVendor(provider);
}
/**
* Remove an {@code EClass} selection strategy provider.
*
* @param provider the provider to remove
*/
void removeEClassSelectionStrategyProvider(EClassSelectionStrategy.Provider provider) {
eclassSelectionStrategyBazaar.removeVendor(provider);
}
/**
* Register a template provider implementation.
*
* @param templateProvider the {@link TemplateProvider} to add
*/
@Reference(cardinality = ReferenceCardinality.MULTIPLE)
void registerTemplateProvider(TemplateProvider templateProvider) {
templateProviders.add(templateProvider);
}
/**
* Unregister a template provider registration.
*
* @param templateProvider the {@link TemplateProvider} to remove
*/
void unregisterTemplateProvider(TemplateProvider templateProvider) {
templateProviders.remove(templateProvider);
}
/**
* Add a template filter service provider.
*
* @param provider the provider to add
*
* @since 1.21
*/
@Reference(cardinality = MULTIPLE, policy = DYNAMIC)
public void addFilterServiceProvider(TemplateFilterService.Provider provider) {
templateFilterBazaar.addVendor(provider);
}
/**
* Remove a template filter service provider.
*
* @param provider the provider to remove
*/
void removeFilterServiceProvider(TemplateFilterService.Provider provider) {
templateFilterBazaar.removeVendor(provider);
}
/**
* Called by the framework to set the {@link EMFFormsLocalizationService}.
*
* @param localizationService The {@link EMFFormsLocalizationService}
*/
@Reference
void setLocalizationService(EMFFormsLocalizationService localizationService) {
this.localizationService = localizationService;
}
@Override
protected boolean handles(EObject owner, EReference reference) {
for (final TemplateProvider provider : templateProviders) {
if (provider.canProvideTemplates(owner, reference)) {
return true;
}
}
return false;
}
/**
* Collect a list of available templates for the given {@link EReference}.
*
* @param eObject the parent {@link EObject}
* @param eReference the {@link EReference} to find templates for
* @return list of available templates
*/
protected Set<Template> collectAvailableTemplates(EObject eObject, EReference eReference) {
final Set<Template> templates = new LinkedHashSet<Template>();
final Predicate<? super Template> filter = getFilter(eObject, eReference);
for (final TemplateProvider provider : templateProviders) {
if (!provider.canProvideTemplates(eObject, eReference)) {
continue;
}
Set<Template> providedTemplates;
if (provider instanceof BlankTemplateProvider) {
final EClassSelectionStrategy eClassSelectionStrategy = ReferenceStrategyUtil
.createDynamicEClassSelectionStrategy(eclassSelectionStrategyBazaar, context);
providedTemplates = ((BlankTemplateProvider) provider).provideTemplates(eObject, eReference,
eClassSelectionStrategy);
} else {
providedTemplates = provider.provideTemplates(eObject, eReference);
}
if (filter == null) {
templates.addAll(providedTemplates);
} else {
providedTemplates.stream().filter(filter).forEach(templates::add);
}
}
return templates;
}
/**
* Creates the {@link CreateNewModelElementStrategy}.
*
* @return The created {@link CreateNewModelElementStrategy}
*/
@Create
public CreateNewModelElementStrategy createCreateNewModelElementStrategy() {
final EClassSelectionStrategy eClassSelectionStrategy = ReferenceStrategyUtil
.createDynamicEClassSelectionStrategy(eclassSelectionStrategyBazaar, context);
return new Strategy(eClassSelectionStrategy);
}
/**
* Obtain a filter predicate for the templates provided for the given {@code reference}
* of an {@code owner} object in the editor.
*
* @param owner the {@link EObject} in the model
* @param reference the {@link EReference} in which templates are to be instantiated
*
* @return the filter, or {@code null} if none
*
* @since 1.21
*/
protected Predicate<? super Template> getFilter(EObject owner, EReference reference) {
final BazaarContext bazaarContext = ReferenceStrategyUtil.createBazaarContext(context, owner, reference);
return templateFilterBazaar.createProducts(bazaarContext).stream()
.map(service -> service.getTemplateFilter(owner, reference))
.filter(Objects::nonNull)
.reduce(Predicate::and)
.orElse(null);
}
/**
* The actual {@link CreateNewModelElementStrategy strategy} that creates a new element based on a template selected
* by the user.
*
* @author Lucas Koehler
*/
class Strategy implements CreateNewModelElementStrategy {
private final EClassSelectionStrategy classSelectionStrategy;
/**
* Initializes me with a strategy for selecting classes permitted in the particular form context.
*
* @param classSelectionStrategy my class selection strategy
*/
Strategy(final EClassSelectionStrategy classSelectionStrategy) {
super();
this.classSelectionStrategy = classSelectionStrategy;
}
/**
* {@inheritDoc}
* <p>
* Create the new model element based on a template.
*/
@Override
public Optional<EObject> createNewModelElement(EObject owner, EReference reference) {
final Set<Template> availableTemplates = collectAvailableTemplates(owner, reference);
if (availableTemplates.isEmpty()) {
// This should not happen because in case of no available templates, the provider should not bid during
// the strategy selection
return Optional.empty();
}
final Set<EClass> availableClasses = new LinkedHashSet<EClass>(
EMFUtils.getSubClasses(reference.getEReferenceType()));
final Set<EClass> subClasses = new LinkedHashSet<EClass>(
classSelectionStrategy.collectEClasses(owner, reference, availableClasses));
Template selected = availableTemplates.iterator().next();
if (availableTemplates.size() > 1) {
// Don't show classes for which we don't have templates (bug 543461)
final Set<EClass> templateClasses = availableTemplates.stream()
.map(Template::getInstance).filter(Objects::nonNull).map(EObject::eClass)
.collect(Collectors.toSet());
subClasses.retainAll(templateClasses);
final Optional<Template> selectedElement = showSelectModelInstancesDialog(subClasses,
availableTemplates);
if (selectedElement.isPresent()) {
selected = selectedElement.get();
} else {
return Optional.empty();
}
}
return Optional.of(EcoreUtil.copy(selected.getInstance()));
}
/**
* Show a model instance selection dialog to first select a sub class and then a fitting template.
*
* @param subClasses the sub classes to choose from
* @param availableInstances the instances to choose from
* @return the instances selected by the user
*/
protected Optional<Template> showSelectModelInstancesDialog(Set<EClass> subClasses,
Set<Template> availableInstances) {
final SelectSubclassAndTemplateWizard wizard = new SelectSubclassAndTemplateWizard(
localizationService.getString(TemplateCreateNewModelElementStrategyProvider.class,
MessageKeys.TemplateCreateNewModelElementStrategyProvider_wizardTitle),
subClasses, availableInstances, localizationService);
final WizardDialog wd = new WizardDialog(Display.getDefault().getActiveShell(), wizard);
wd.open();
return wizard.getSelectedTemplate();
}
}
}