blob: 7b5670c255353dc6554f6c60796f5d1b81f49577 [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:
* Eugen Neufeld - initial API and implementation
* Christian W. Damus - bugs 527740, 533522, 545686
******************************************************************************/
package org.eclipse.emf.ecp.view.internal.context;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecp.common.spi.UniqueSetting;
import org.eclipse.emf.ecp.view.spi.context.EMFFormsLegacyServicesManager;
import org.eclipse.emf.ecp.view.spi.context.GlobalViewModelService;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContextDisposeListener;
import org.eclipse.emf.ecp.view.spi.context.ViewModelService;
import org.eclipse.emf.ecp.view.spi.context.ViewModelServiceNotAvailableReport;
import org.eclipse.emf.ecp.view.spi.context.ViewModelServiceProvider;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeAddRemoveListener;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeListener;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeNotification;
import org.eclipse.emf.ecp.view.spi.model.VControl;
import org.eclipse.emf.ecp.view.spi.model.VDomainModelReference;
import org.eclipse.emf.ecp.view.spi.model.VDomainModelReferenceSegment;
import org.eclipse.emf.ecp.view.spi.model.VElement;
import org.eclipse.emf.ecp.view.spi.model.VView;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emfforms.common.Optional;
import org.eclipse.emfforms.spi.common.report.AbstractReport;
import org.eclipse.emfforms.spi.common.report.ReportService;
import org.eclipse.emfforms.spi.core.services.controlmapper.EMFFormsSettingToControlMapper;
import org.eclipse.emfforms.spi.core.services.domainexpander.EMFFormsDomainExpander;
import org.eclipse.emfforms.spi.core.services.domainexpander.EMFFormsExpandingFailedException;
import org.eclipse.emfforms.spi.core.services.view.EMFFormsContextListener;
import org.eclipse.emfforms.spi.core.services.view.EMFFormsViewServiceManager;
import org.eclipse.emfforms.spi.core.services.view.RootDomainModelChangeListener;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
/**
* The Class ViewModelContextImpl.
*
* @author Eugen Neufeld
*/
public class ViewModelContextImpl implements ViewModelContext {
static {
final Bundle bundle = FrameworkUtil.getBundle(ViewModelContextImpl.class);
if (bundle != null) {
bundleContext = bundle.getBundleContext();
}
}
private static BundleContext bundleContext;
private static final String MODEL_CHANGE_LISTENER_MUST_NOT_BE_NULL = "ModelChangeAddRemoveListener must not be null."; //$NON-NLS-1$
private static final String ROOT_DOMAIN_MODEL_CHANGE_LISTENER_MUST_NOT_BE_NULL = "RootDomainModelChangeListener must not be null."; //$NON-NLS-1$
private static final String THE_VIEW_MODEL_CONTEXT_WAS_ALREADY_DISPOSED = "The ViewModelContext was already disposed."; //$NON-NLS-1$
/** The view. */
private final VElement view;
/** The domain object. */
private EObject domainObject;
/** The view model change listener. Needs to be thread safe. */
private final List<ModelChangeListener> viewModelChangeListener = new CopyOnWriteArrayList<ModelChangeListener>();
/** The domain model change listeners. Needs to be thread safe. */
private final GroupedListenerList<ModelChangeListener> domainModelChangeListener =
// needed to make sure that all data operations are done before any validation etc provided by services happens
new GroupedListenerList<ModelChangeListener>(VDomainModelReference.class);
/** The root domain model change listeners. Needs to be thread safe. */
private final List<RootDomainModelChangeListener> rootDomainModelChangeListeners = new CopyOnWriteArrayList<RootDomainModelChangeListener>();
/** The domain model content adapter. */
private EContentAdapter domainModelContentAdapter;
/** The view model content adapter. */
private EContentAdapter viewModelContentAdapter;
private final Set<EMFFormsContextListener> contextListeners = new CopyOnWriteArraySet<EMFFormsContextListener>();
/** The view services. */
private final SortedSet<ViewModelService> viewServices = new TreeSet<ViewModelService>(
new Comparator<ViewModelService>() {
@Override
public int compare(ViewModelService arg0, ViewModelService arg1) {
if (arg0.getPriority() == arg1.getPriority()) {
/* compare would return 0, meaning the services are identical -> 1 service would get lost */
return arg0.getClass().getName().compareTo(arg1.getClass().getName());
}
return arg0.getPriority() - arg1.getPriority();
}
});
/** Provider of view service overrides. */
private final ViewModelServiceProvider viewServiceProvider;
/**
* The disposed state.
*/
private boolean isDisposed;
/**
* The context map.
*/
private final Map<String, Object> keyObjectMap = new LinkedHashMap<String, Object>();
/**
* Whether the context is being disposed.
*/
private boolean isDisposing;
private Resource resource;
private final Map<EObject, Set<ViewModelContext>> childContexts = new LinkedHashMap<EObject, Set<ViewModelContext>>();
private final Map<ViewModelContext, VElement> childContextUsers = new LinkedHashMap<ViewModelContext, VElement>();
private final ViewModelContext parentContext;
private final Set<ViewModelContextDisposeListener> disposeListeners = new LinkedHashSet<ViewModelContextDisposeListener>();
// TODO replace with eclipse context?!
private final Map<Class<?>, Object> serviceMap = new LinkedHashMap<Class<?>, Object>();
private final Map<ServiceReference<?>, Class<?>> usedOSGiServices = new LinkedHashMap<ServiceReference<?>, Class<?>>();
private ServiceListener serviceListener;
private final VElement parentVElement;
/**
* Instantiates a new view model context impl.
*
* @param view the view
* @param domainObject the domain object
*/
public ViewModelContextImpl(VElement view, EObject domainObject) {
this(view, domainObject, ViewModelServiceProvider.NULL);
}
/**
* Instantiates a new view model context impl.
*
* @param view the view
* @param domainObject the domain object
* @param parent The parent {@link ViewModelContext}
* @param parentVElement the parent {@link VElement}
*/
public ViewModelContextImpl(VElement view, EObject domainObject, ViewModelContext parent,
VElement parentVElement) {
this(view, domainObject, parent, parentVElement, ViewModelServiceProvider.NULL);
}
/**
* Instantiates a new view model context impl.
*
* @param view the view
* @param domainObject the domain object
* @param modelServices an array of services to use in the {@link ViewModelContext}
*
* @deprecated As of 1.16, use the {@link #ViewModelContextImpl(VElement, EObject, ViewModelServiceProvider)} API
*/
@Deprecated
public ViewModelContextImpl(VElement view, EObject domainObject, ViewModelService... modelServices) {
this(view, domainObject, new ArrayOnceViewModelServiceProvider(modelServices));
}
/**
* Instantiates a new view model context impl.
*
* @param view the view
* @param domainObject the domain object
* @param modelServiceProvider a provider of services to use in the {@link ViewModelContext}. May be {@code null} if
* local service overrides are not needed
*
* @since 1.16
*/
public ViewModelContextImpl(VElement view, EObject domainObject, ViewModelServiceProvider modelServiceProvider) {
this(view, domainObject, null, null, modelServiceProvider);
}
/**
* Instantiates a new view model context impl with a parent context.
*
* @param view the view
* @param domainObject the domain object
* @param parent The parent {@link ViewModelContext}
* @param parentVElement The parent {@link VElement}
* @param modelServices an array of services to use in the {@link ViewModelContext}
*
* @deprecated As of 1.16, use the
* {@link #ViewModelContextImpl(VElement, EObject, ViewModelContext, VElement, ViewModelServiceProvider)}
* API
*/
@Deprecated
public ViewModelContextImpl(VElement view, EObject domainObject, ViewModelContext parent,
VElement parentVElement, ViewModelService... modelServices) {
this(view, domainObject, parent, parentVElement, new ArrayOnceViewModelServiceProvider(modelServices));
}
/**
* Instantiates a new view model context impl with a parent context.
*
* @param view the view
* @param domainObject the domain object
* @param parent The parent {@link ViewModelContext}
* @param parentVElement The parent {@link VElement}
* @param modelServiceProvider a provider of services to use in the {@link ViewModelContext}. May be {@code null} if
* local service overrides are not needed
*
* @since 1.16
*/
public ViewModelContextImpl(VElement view, EObject domainObject, ViewModelContext parent,
VElement parentVElement, ViewModelServiceProvider modelServiceProvider) {
this(view, domainObject, parent, parentVElement, modelServiceProvider, null);
}
/**
* Instantiates a new view model context with initial {@linkplain #getContextValue(String) context values}.
*
* @param view the view
* @param domainObject the domain object
* @param contextValues initial context values to set
*
* @since 1.21
* @see #getContextValue(String)
*/
public ViewModelContextImpl(VElement view, EObject domainObject, Map<String, ?> contextValues) {
this(view, domainObject, ViewModelServiceProvider.NULL, contextValues);
}
/**
* Instantiates a new view model context with initial {@linkplain #getContextValue(String) context values}.
*
* @param view the view
* @param domainObject the domain object
* @param modelServiceProvider a provider of services to use in the {@link ViewModelContext}. May be {@code null} if
* local service overrides are not needed
* @param contextValues initial context values to set
*
* @since 1.21
* @see #getContextValue(String)
*/
public ViewModelContextImpl(VElement view, EObject domainObject, ViewModelServiceProvider modelServiceProvider,
Map<String, ?> contextValues) {
this(view, domainObject, null, null, modelServiceProvider, contextValues);
}
/**
* Internal constructor to which all others ultimately delegate.
*/
private ViewModelContextImpl(VElement view, EObject domainObject, ViewModelContext parent,
VElement parentVElement, ViewModelServiceProvider modelServiceProvider, Map<String, ?> contextValues) {
this.view = view;
this.domainObject = domainObject;
parentContext = parent;
if (modelServiceProvider == null) {
viewServiceProvider = ViewModelServiceProvider.NULL;
} else {
viewServiceProvider = modelServiceProvider;
}
this.parentVElement = parentVElement;
// This must be done before instantiating services that may use the context values
if (contextValues != null) {
if (parentContext != null) {
// Values go up to the parent
contextValues.forEach(parentContext::putContextValue);
} else {
// I am the root
keyObjectMap.putAll(contextValues);
}
}
instantiate();
}
/**
* Resolve all domain model references for a given resolvable and a given domain model root.
*
* @param resolvable The EObject to resolve all {@link VDomainModelReference domain model references} of.
* @param domainModelRoot the domain model used for the resolving.
* @throws EMFFormsExpandingFailedException If the domain expansion fails.
*/
private void resolveDomainReferences(EObject resolvable, EObject domainModelRoot) {
// Get domain expander service
final EMFFormsDomainExpander domainExpander = getService(EMFFormsDomainExpander.class);
if (domainExpander == null) {
return;
}
expandAndInitDMR(domainModelRoot, domainExpander, resolvable);
// Iterate over all domain model references of the given EObject.
final TreeIterator<EObject> eAllContents = resolvable.eAllContents();
while (eAllContents.hasNext()) {
final EObject eObject = eAllContents.next();
expandAndInitDMR(domainModelRoot, domainExpander, eObject);
}
}
private void expandAndInitDMR(EObject domainModelRoot, final EMFFormsDomainExpander domainExpander,
final EObject eObject) {
if (VDomainModelReference.class.isInstance(eObject)
&& !VDomainModelReference.class.isInstance(eObject.eContainer())
&& !VDomainModelReferenceSegment.class.isInstance(eObject.eContainer())) {
final VDomainModelReference domainModelReference = VDomainModelReference.class.cast(eObject);
try {
domainExpander.prepareDomainObject(domainModelReference, domainModelRoot);
} catch (final EMFFormsExpandingFailedException ex) {
getServiceWithoutLog(ReportService.class).report(new AbstractReport(ex));
}
}
}
/**
* Instantiate.
*/
private void instantiate() {
addResourceIfNecessary();
resolveDomainReferences(getViewModel(), getDomainModel());
if (parentContext == null) {
domainModelContentAdapter = new DomainModelContentAdapter();
domainObject.eAdapters().add(domainModelContentAdapter);
}
loadImmediateServices();
viewModelContentAdapter = new ViewModelContentAdapter();
view.eAdapters().add(viewModelContentAdapter);
// Generate local view services from our provider
viewServices.addAll(viewServiceProvider.getViewModelServices(view, domainObject));
for (final ViewModelService viewService : viewServices) {
viewService.instantiate(this);
}
for (final EMFFormsContextListener contextListener : contextListeners) {
contextListener.contextInitialised();
}
}
private void loadImmediateServices() {
final Bundle bundle = FrameworkUtil.getBundle(getClass());
if (bundle != null) {
final BundleContext bundleContext = bundle.getBundleContext();
serviceReferenceLegacy = bundleContext
.getServiceReference(EMFFormsLegacyServicesManager.class);
if (serviceReferenceLegacy != null) {
final EMFFormsLegacyServicesManager legacyServicesFactory = bundleContext
.getService(serviceReferenceLegacy);
legacyServicesFactory.instantiate();
}
servicesManager = getService(EMFFormsViewServiceManager.class);
for (final Class<?> localImmediateService : servicesManager.getAllLocalImmediateServiceTypes()) {
final Optional<?> service = servicesManager.createLocalImmediateService(localImmediateService, this);
if (!service.isPresent()) {
// error case?
continue;
}
serviceMap.put(localImmediateService, service.get());
}
if (parentContext == null) {
for (final Class<?> globalImmediateService : servicesManager.getAllGlobalImmediateServiceTypes()) {
final Optional<?> service = servicesManager.createGlobalImmediateService(globalImmediateService,
this);
if (!service.isPresent()) {
// error case?
continue;
}
final Object serviceObject = service.get();
serviceMap.put(globalImmediateService, serviceObject);
}
}
serviceListener = new ServiceListener() {
@Override
public void serviceChanged(ServiceEvent event) {
if (event.getType() == ServiceEvent.UNREGISTERING &&
usedOSGiServices.containsKey(event.getServiceReference())) {
final Class<?> remove = usedOSGiServices.remove(event.getServiceReference());
serviceMap.remove(remove);
}
}
};
bundleContext.addServiceListener(serviceListener);
}
}
private void addResourceIfNecessary() {
if (domainObject.eResource() != null) {
return;
}
final EObject rootObject = EcoreUtil.getRootContainer(domainObject);
final ResourceSet rs = new ResourceSetImpl();
final ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(new AdapterFactory[] {
new ReflectiveItemProviderAdapterFactory(),
new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE) });
final AdapterFactoryEditingDomain domain = new AdapterFactoryEditingDomain(
adapterFactory,
new BasicCommandStack(), rs);
rs.eAdapters().add(new AdapterFactoryEditingDomain.EditingDomainProvider(domain));
resource = rs.createResource(URI.createURI("VIRTAUAL_URI")); //$NON-NLS-1$
if (resource != null) {
resource.getContents().add(rootObject);
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.context.ViewModelContext#getControlsFor(org.eclipse.emf.ecore.EStructuralFeature.Setting)
* @deprecated
*/
@Deprecated
@Override
public Set<VControl> getControlsFor(Setting setting) {
final Set<VElement> elements = getService(EMFFormsSettingToControlMapper.class)
.getControlsFor(UniqueSetting.createSetting(setting));
final Set<VControl> controls = new LinkedHashSet<VControl>();
for (final VElement element : elements) {
if (VControl.class.isInstance(element)) {
controls.add((VControl) element);
}
}
return controls;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.context.ViewModelContext#getControlsFor(org.eclipse.emf.ecp.common.spi.UniqueSetting)
* @deprecated
*/
@Deprecated
@Override
public Set<VElement> getControlsFor(UniqueSetting setting) {
return getService(EMFFormsSettingToControlMapper.class).getControlsFor(setting);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.context.ViewModelContext#getViewModel()
*/
@Override
public VElement getViewModel() {
if (isDisposed) {
throw new IllegalStateException(THE_VIEW_MODEL_CONTEXT_WAS_ALREADY_DISPOSED);
}
return view;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.context.ViewModelContext#getDomainModel()
*/
@Override
public EObject getDomainModel() {
if (isDisposed) {
throw new IllegalStateException(THE_VIEW_MODEL_CONTEXT_WAS_ALREADY_DISPOSED);
}
return domainObject;
}
/**
* Dispose.
*/
@Override
public void dispose() {
if (isDisposed) {
return;
}
isDisposing = true;
for (final ViewModelContextDisposeListener listener : disposeListeners) {
listener.contextDisposed(this);
}
innerDispose();
viewModelChangeListener.clear();
rootDomainModelChangeListeners.clear();
isDisposing = false;
isDisposed = true;
if (serviceReferenceLegacy != null) {
bundleContext.ungetService(serviceReferenceLegacy);
}
}
private void releaseOSGiServices() {
final Bundle bundle = FrameworkUtil.getBundle(getClass());
if (bundle != null) {
final BundleContext bundleContext = bundle.getBundleContext();
for (final ServiceReference<?> serviceReference : usedOSGiServices.keySet()) {
bundleContext.ungetService(serviceReference);
}
}
usedOSGiServices.clear();
}
@Override
public void registerViewChangeListener(ModelChangeListener modelChangeListener) {
if (isDisposed) {
throw new IllegalStateException(THE_VIEW_MODEL_CONTEXT_WAS_ALREADY_DISPOSED);
}
if (modelChangeListener == null) {
throw new IllegalArgumentException(MODEL_CHANGE_LISTENER_MUST_NOT_BE_NULL);
}
viewModelChangeListener.add(modelChangeListener);
}
@Override
public void unregisterViewChangeListener(ModelChangeListener modelChangeListener) {
// if (isDisposed) {
// throw new IllegalStateException("The ViewModelContext was already disposed.");
// }
viewModelChangeListener.remove(modelChangeListener);
}
@Override
public void registerDomainChangeListener(ModelChangeListener modelChangeListener) {
if (isDisposed) {
throw new IllegalStateException(THE_VIEW_MODEL_CONTEXT_WAS_ALREADY_DISPOSED);
}
if (modelChangeListener == null) {
throw new IllegalArgumentException(MODEL_CHANGE_LISTENER_MUST_NOT_BE_NULL);
}
if (parentContext == null) {
domainModelChangeListener.add(modelChangeListener);
} else {
parentContext.registerDomainChangeListener(modelChangeListener);
}
}
@Override
public void unregisterDomainChangeListener(ModelChangeListener modelChangeListener) {
// if (isDisposed) {
// throw new IllegalStateException("The ViewModelContext was already disposed.");
// }
if (modelChangeListener == null) {
// ConcurrentSkipListSet doesn't allow nulls but balks on attempts to remove them, too
return;
}
if (parentContext == null) {
domainModelChangeListener.remove(modelChangeListener);
} else {
parentContext.unregisterDomainChangeListener(modelChangeListener);
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.context.ViewModelContext#hasService(java.lang.Class)
*/
@Override
public <T> boolean hasService(Class<T> serviceType) {
return getServiceWithoutLog(serviceType) != null;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.context.ViewModelContext#getService(java.lang.Class)
*/
@Override
public <T> T getService(Class<T> serviceType) {
final T service = getServiceWithoutLog(serviceType);
if (service == null) {
report(new ViewModelServiceNotAvailableReport(serviceType));
}
return service;
}
/**
* Retrieve an {@link ViewModelService} of type {@code serviceType} without reporting an error if none is found.
*
* @param <T>
* the type of the desired service
*
* @param serviceType
* the type of the service to be retrieved
* @return the service
*/
@SuppressWarnings("unchecked")
protected <T> T getServiceWithoutLog(Class<T> serviceType) {
// legacy stuff
for (final ViewModelService service : viewServices) {
if (serviceType.isInstance(service)) {
return (T) service;
}
}
// First check local services
final T localService = getLocalService(serviceType);
if (localService != null) {
return localService;
}
// If context is the root, check global services to be instanciated
if (servicesManager != null && parentContext == null) {
final Optional<T> lazyService = servicesManager.createGlobalLazyService(serviceType, this);
if (lazyService.isPresent()) {
final T t = lazyService.get();
serviceMap.put(serviceType, t);
return t;
}
}
// Check the parent context
if (parentContext != null && parentContext.hasService(serviceType)) {
return parentContext.getService(serviceType);
}
// Check OSGi services
if (bundleContext != null) {
final ServiceReference<T> serviceReference = bundleContext.getServiceReference(serviceType);
if (serviceReference != null) {
usedOSGiServices.put(serviceReference, serviceType);
final T service = bundleContext.getService(serviceReference);
serviceMap.put(serviceType, service);
return service;
}
}
return null;
}
/**
* @return a instance of a local service or a local service service, which can be created or null if neither exists.
*/
@SuppressWarnings("unchecked")
private <T> T getLocalService(Class<T> serviceType) {
if (serviceMap.containsKey(serviceType)) {
return (T) serviceMap.get(serviceType);
} else if (servicesManager != null) {
final Optional<T> lazyService = servicesManager.createLocalLazyService(serviceType, this);
if (lazyService.isPresent()) {
final T t = lazyService.get();
serviceMap.put(serviceType, t);
return t;
}
}
return null;
}
private void report(AbstractReport report) {
final ReportService reportService = getServiceWithoutLog(ReportService.class);
if (reportService != null) {
reportService.report(report);
}
}
/**
* The content adapter for the view model.
*/
private class ViewModelContentAdapter extends EContentAdapter {
@Override
public void notifyChanged(Notification notification) {
super.notifyChanged(notification);
// do not notify while being disposed
if (isDisposing) {
return;
}
if (isDisposed) {
return;
}
if (notification.isTouch()) {
return;
}
final ModelChangeNotification modelChangeNotification = new ModelChangeNotification(notification);
for (final ModelChangeListener modelChangeListener : viewModelChangeListener) {
modelChangeListener.notifyChange(modelChangeNotification);
}
}
@Override
protected void addAdapter(Notifier notifier) {
super.addAdapter(notifier);
// do not notify while being disposed
if (isDisposing || isDisposed) {
return;
}
for (final ModelChangeListener modelChangeListener : viewModelChangeListener) {
if (ModelChangeAddRemoveListener.class.isInstance(modelChangeListener)) {
ModelChangeAddRemoveListener.class.cast(modelChangeListener).notifyAdd(notifier);
}
}
}
@Override
protected void removeAdapter(Notifier notifier) {
super.removeAdapter(notifier);
// do not notify while being disposed
if (isDisposing) {
return;
}
if (VElement.class.isInstance(notifier)) {
VElement.class.cast(notifier).setDiagnostic(null);
}
for (final ModelChangeListener modelChangeListener : viewModelChangeListener) {
if (ModelChangeAddRemoveListener.class.isInstance(modelChangeListener)) {
ModelChangeAddRemoveListener.class.cast(modelChangeListener).notifyRemove(notifier);
}
}
}
}
/**
* The content adapter for the domain model.
*/
private class DomainModelContentAdapter extends EContentAdapter {
@Override
public void notifyChanged(Notification notification) {
super.notifyChanged(notification);
// do not notify while being disposed
if (isDisposing) {
return;
}
final ModelChangeNotification modelChangeNotification = new ModelChangeNotification(notification);
for (final ModelChangeListener modelChangeListener : domainModelChangeListener) {
modelChangeListener.notifyChange(modelChangeNotification);
}
}
@Override
protected void addAdapter(Notifier notifier) {
super.addAdapter(notifier);
// do not notify while being disposed
if (isDisposing) {
return;
}
for (final ModelChangeListener modelChangeListener : domainModelChangeListener) {
if (ModelChangeAddRemoveListener.class.isInstance(modelChangeListener)) {
ModelChangeAddRemoveListener.class.cast(modelChangeListener).notifyAdd(notifier);
}
}
}
@Override
protected void removeAdapter(Notifier notifier) {
super.removeAdapter(notifier);
// do not notify while being disposed
if (isDisposing) {
return;
}
for (final ModelChangeListener modelChangeListener : domainModelChangeListener) {
if (ModelChangeAddRemoveListener.class.isInstance(modelChangeListener)) {
ModelChangeAddRemoveListener.class.cast(modelChangeListener).notifyRemove(notifier);
}
}
}
}
private final Set<Object> users = new LinkedHashSet<Object>();
private EMFFormsViewServiceManager servicesManager;
private ServiceReference<EMFFormsLegacyServicesManager> serviceReferenceLegacy;
/**
* Inner method for registering context users (not {@link ViewModelService}).
*
* @param user the user of the context
*/
@Override
public void addContextUser(Object user) {
users.add(user);
}
/**
* Inner method for unregistering the context user.
*
* @param user the user of the context
*/
@Override
public void removeContextUser(Object user) {
users.remove(user);
// Every renderer is registered here, as it needs to know when the view model changes (rules, validations, etc).
// If no listener is left, we can dispose the context
// if (users.isEmpty() || users.size() == 1 && parentContext != null && users.contains(parentContext)) {
if (users.isEmpty()) {
dispose();
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.context.ViewModelContext#getContextValue(java.lang.String)
*/
@Override
public Object getContextValue(String key) {
if (parentContext != null) {
return parentContext.getContextValue(key);
}
return keyObjectMap.get(key);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.context.ViewModelContext#putContextValue(java.lang.String, java.lang.Object)
*/
@Override
public void putContextValue(String key, Object value) {
if (parentContext != null) {
parentContext.putContextValue(key, value);
return;
}
keyObjectMap.put(key, value);
}
private void addChildContext(VElement vElement, EObject eObject, ViewModelContext childContext) {
if (!childContexts.containsKey(eObject)) {
childContexts.put(eObject, new LinkedHashSet<ViewModelContext>());
}
childContexts.get(eObject).add(childContext);
childContextUsers.put(childContext, vElement);
for (final EMFFormsContextListener contextListener : contextListeners) {
contextListener.childContextAdded(vElement, childContext);
}
// notify all global View model services
for (final ViewModelService viewModelService : viewServices) {
if (GlobalViewModelService.class.isInstance(viewModelService)) {
GlobalViewModelService.class.cast(viewModelService).childViewModelContextAdded(childContext);
}
}
}
private void removeChildContext(EObject eObject, ViewModelContext context) {
final boolean removed = childContexts.get(eObject).remove(context);
if (removed) {
childContextUsers.remove(context);
}
if (childContexts.get(eObject).size() == 0) {
childContexts.remove(eObject);
}
}
/**
* Obtains the provider of local view-model service overrides.
*
* @return the local view-model service provider
*
* @since 1.16
*/
protected ViewModelServiceProvider getViewModelServiceProvider() {
return viewServiceProvider;
}
@Deprecated
@Override
public ViewModelContext getChildContext(final EObject eObject, VElement parent, VView vView,
ViewModelService... viewModelServices) {
return getChildContext(eObject, parent, vView, new ArrayOnceViewModelServiceProvider(viewModelServices));
}
@Override
public ViewModelContext getChildContext(final EObject eObject, VElement parent, VView vView,
ViewModelServiceProvider viewModelServiceProvider) {
final Set<ViewModelContext> contexts = childContexts.get(eObject);
if (contexts != null) {
for (final ViewModelContext context : contexts) {
// TODO change to use bidirectional map
if (childContextUsers.get(context).equals(parent)) {
return context;
}
}
}
// no context found -> create a new one
ViewModelServiceProvider serviceProvider = getViewModelServiceProvider();
if (viewModelServiceProvider != null) {
// Compose the client's provided service first as they must override ours
serviceProvider = new ViewModelServiceProvider.Composed(
viewModelServiceProvider, serviceProvider);
}
final ViewModelContext childContext = new ViewModelContextImpl(vView, eObject, this, parent, serviceProvider);
childContext.registerDisposeListener(new ViewModelContextDisposeListener() {
@Override
public void contextDisposed(ViewModelContext viewModelContext) {
removeChildContext(eObject, viewModelContext);
for (final EMFFormsContextListener contextListener : contextListeners) {
contextListener.childContextDisposed(viewModelContext);
}
}
});
addChildContext(parent, eObject, childContext);
return childContext;
}
@Override
public ViewModelContext getParentContext() {
return parentContext;
}
@Override
public VElement getParentVElement() {
return parentVElement;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.context.ViewModelContext#registerDisposeListener(org.eclipse.emf.ecp.view.spi.context.ViewModelContextDisposeListener)
*/
@Override
public void registerDisposeListener(ViewModelContextDisposeListener listener) {
disposeListeners.add(listener);
}
@Override
public void registerEMFFormsContextListener(EMFFormsContextListener contextListener) {
contextListeners.add(contextListener);
}
@Override
public void unregisterEMFFormsContextListener(EMFFormsContextListener contextListener) {
contextListeners.remove(contextListener);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emfforms.spi.core.services.view.EMFFormsViewContext#changeDomainModel(org.eclipse.emf.ecore.EObject)
*/
@Override
public void changeDomainModel(EObject newDomainModel) {
if (isDisposed) {
throw new IllegalStateException(THE_VIEW_MODEL_CONTEXT_WAS_ALREADY_DISPOSED);
}
// =============
// DISPOSE CODE
innerDispose();
// =======================
domainObject = newDomainModel;
// re-instantiate
instantiate();
for (final RootDomainModelChangeListener listener : rootDomainModelChangeListeners) {
listener.notifyChange();
}
}
private void innerDispose() {
if (resource != null) {
resource.getContents().remove(domainObject);
}
view.eAdapters().remove(viewModelContentAdapter);
domainObject.eAdapters().remove(domainModelContentAdapter);
domainModelChangeListener.clear();
for (final ViewModelService viewService : viewServices) {
viewService.dispose();
}
viewServices.clear();
for (final EMFFormsContextListener contextListener : contextListeners) {
contextListener.contextDispose();
}
// TODO Child context disposing necessary?
final Set<ViewModelContext> toDispose = new LinkedHashSet<ViewModelContext>(childContextUsers.keySet());
for (final ViewModelContext vmc : toDispose) {
vmc.dispose();
}
childContextUsers.clear();
childContexts.clear();
releaseOSGiServices();
serviceMap.clear();
final Bundle bundle = FrameworkUtil.getBundle(getClass());
if (bundle != null) {
final BundleContext bundleContext = bundle.getBundleContext();
bundleContext.removeServiceListener(serviceListener);
serviceListener = null;
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emfforms.spi.core.services.view.EMFFormsViewContext#registerRootDomainModelChangeListener(org.eclipse.emfforms.spi.core.services.view.RootDomainModelChangeListener)
*/
@Override
public void registerRootDomainModelChangeListener(RootDomainModelChangeListener rootDomainModelChangeListener) {
if (isDisposed) {
throw new IllegalStateException(THE_VIEW_MODEL_CONTEXT_WAS_ALREADY_DISPOSED);
}
if (rootDomainModelChangeListener == null) {
throw new IllegalArgumentException(ROOT_DOMAIN_MODEL_CHANGE_LISTENER_MUST_NOT_BE_NULL);
}
rootDomainModelChangeListeners.add(rootDomainModelChangeListener);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emfforms.spi.core.services.view.EMFFormsViewContext#unregisterRootDomainModelChangeListener(org.eclipse.emfforms.spi.core.services.view.RootDomainModelChangeListener)
*/
@Override
public void unregisterRootDomainModelChangeListener(RootDomainModelChangeListener rootDomainModelChangeListener) {
rootDomainModelChangeListeners.remove(rootDomainModelChangeListener);
}
/**
* A collection of listeners grouped by class. Work-around for the absence of priority
* support (<a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=460158">bug 460158</a>).
*
* @author Christian W. Damus
*
* @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=460158">bug 460158</a>
*/
private static final class GroupedListenerList<T> implements Iterable<T> {
private final Set<T> listeners = new ConcurrentSkipListSet<T>(comparator());
private final AtomicInteger nextGroup = new AtomicInteger();
private final ConcurrentMap<Class<?>, Integer> groups = new ConcurrentHashMap<Class<?>, Integer>();
private final Class<?> importantGroup;
/**
* Initializes with an important group that must be first in the iteration order.
*/
GroupedListenerList(Class<?> importantGroup) {
super();
this.importantGroup = importantGroup;
seedImportantGroup();
}
/**
* Sort elements by group first and then arbitrarily within each group.
*
* @return a grouping comparator
*/
private Comparator<T> comparator() {
return new Comparator<T>() {
@Override
public int compare(T o1, T o2) {
if (o1 == o2) {
return 0;
}
final int group1 = getGroup(o1);
final int group2 = getGroup(o2);
int result = group1 - group2;
if (result == 0) {
// Same group. Arbitrary ordering by address
result = System.identityHashCode(o1) - System.identityHashCode(o2);
}
return result;
}
};
}
private void seedImportantGroup() {
if (importantGroup != null) {
// Seed the map with a lowest-order group
groups.put(importantGroup, nextGroup.getAndIncrement());
}
}
private int getGroup(T listener) {
final Class<?> groupKey = importantGroup != null && importantGroup.isInstance(listener)
? importantGroup
: listener.getClass();
Integer result = groups.get(groupKey);
if (result == null) {
result = nextGroup.getAndIncrement();
final Integer collision = groups.putIfAbsent(groupKey, result);
if (collision != null) {
result = collision;
}
}
return result;
}
void add(T listener) {
listeners.add(listener);
// need to add group as soon as listener added to correctly calculate order
getGroup(listener);
}
void remove(T listener) {
listeners.remove(listener);
}
void clear() {
listeners.clear();
groups.clear();
seedImportantGroup();
}
@Override
public Iterator<T> iterator() {
return listeners.iterator();
}
}
}