blob: c21dccd770122290f75c8596abe0bdc75a29092f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2018 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 v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Lucas Koehler- initial API and implementation
* Christian W. Damus - bug 533522
******************************************************************************/
package org.eclipse.emfforms.internal.core.services.controlmapper;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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 org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecp.common.spi.UniqueSetting;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeAddRemoveListener;
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.VElement;
import org.eclipse.emfforms.spi.core.services.controlmapper.EMFFormsSettingToControlMapper;
import org.eclipse.emfforms.spi.core.services.mappingprovider.EMFFormsMappingProviderManager;
import org.eclipse.emfforms.spi.core.services.view.EMFFormsContextListener;
import org.eclipse.emfforms.spi.core.services.view.EMFFormsViewContext;
/**
* Implementation of {@link EMFFormsSettingToControlMapper}.
*
* @author Lucas Koehler
*
*/
public class SettingToControlMapperImpl implements EMFFormsSettingToControlMapper, EMFFormsContextListener {
/**
* Used to get View model changes.
*
* @author Eugen Neufeld
*
*/
private final class ModelChangeAddRemoveListenerImplementation implements ModelChangeAddRemoveListener {
@Override
public void notifyChange(ModelChangeNotification notification) {
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.model.ModelChangeAddRemoveListener#notifyAdd(org.eclipse.emf.common.notify.Notifier)
*/
@Override
public void notifyAdd(Notifier notifier) {
if (VDomainModelReference.class.isInstance(notifier)
&& !VDomainModelReference.class.isInstance(EObject.class.cast(notifier).eContainer())) {
final VDomainModelReference domainModelReference = VDomainModelReference.class.cast(notifier);
final VControl control = findControl(domainModelReference);
if (control != null) {
vControlAdded(control);
}
}
}
private VControl findControl(VDomainModelReference dmr) {
EObject parent = dmr.eContainer();
while (!VControl.class.isInstance(parent) && parent != null) {
parent = parent.eContainer();
}
return (VControl) parent;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecp.view.spi.model.ModelChangeAddRemoveListener#notifyRemove(org.eclipse.emf.common.notify.Notifier)
*/
@Override
public void notifyRemove(Notifier notifier) {
if (VControl.class.isInstance(notifier)) {
vControlRemoved((VControl) notifier);
}
}
}
private final EMFFormsViewContext viewModelContext;
private final ViewModelListener dataModelListener;
private final Map<EObject, Set<UniqueSetting>> eObjectToMappedSettings = new LinkedHashMap<EObject, Set<UniqueSetting>>();
/**
* A mapping between settings and controls.
*/
private final Map<UniqueSetting, List<VElement>> settingToControlMap = new LinkedHashMap<UniqueSetting, List<VElement>>();
private final Map<VElement, List<UniqueSetting>> controlToSettingMap = new LinkedHashMap<VElement, List<UniqueSetting>>();
private final EMFFormsMappingProviderManager mappingManager;
private final Map<VControl, EMFFormsViewContext> controlContextMap = new LinkedHashMap<VControl, EMFFormsViewContext>();
private final Map<EMFFormsViewContext, VElement> contextParentMap = new LinkedHashMap<EMFFormsViewContext, VElement>();
private final Map<EMFFormsViewContext, ViewModelListener> contextListenerMap = new LinkedHashMap<EMFFormsViewContext, ViewModelListener>();
private final ModelChangeAddRemoveListenerImplementation viewModelChangeListener;
private boolean disposed;
/**
* Creates a new instance of {@link SettingToControlMapperImpl}.
*
* @param mappingManager The {@link EMFFormsMappingProviderManager}
* @param viewModelContext The {@link EMFFormsViewContext} that created this instance
*/
public SettingToControlMapperImpl(EMFFormsMappingProviderManager mappingManager,
EMFFormsViewContext viewModelContext) {
this.mappingManager = mappingManager;
this.viewModelContext = viewModelContext;
viewModelContext.registerEMFFormsContextListener(this);
viewModelChangeListener = new ModelChangeAddRemoveListenerImplementation();
viewModelContext.registerViewChangeListener(viewModelChangeListener);
dataModelListener = new ViewModelListener(viewModelContext, this);
}
/**
* {@inheritDoc}
*/
@Override
public Set<VControl> getControlsFor(Setting setting) {
final Set<VControl> result = new LinkedHashSet<VControl>();
final Set<VElement> allElements = getControlsFor(UniqueSetting.createSetting(setting));
for (final VElement element : allElements) {
if (VControl.class.isInstance(element)) {
result.add((VControl) element);
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public Set<VElement> getControlsFor(UniqueSetting setting) {
final Set<VElement> elements = new LinkedHashSet<VElement>();
final List<VElement> currentControls = settingToControlMap.get(setting);
final Set<VControl> controls = new LinkedHashSet<VControl>();
final Set<VElement> validParents = new LinkedHashSet<VElement>();
if (currentControls != null) {
for (final VElement control : currentControls) {
if (!control.isEffectivelyEnabled() || !control.isEffectivelyVisible()
|| control.isEffectivelyReadonly()) {
continue;
}
if (VControl.class.isInstance(control)) {
controls.add((VControl) control);
if (controlContextMap.containsKey(control)) {
final VElement parent = contextParentMap.get(controlContextMap.get(control));
validParents.add(parent);
}
} else {
elements.add(control);
}
}
}
if (controls.isEmpty()) {
return Collections.emptySet();
}
final Set<VElement> result = new LinkedHashSet<VElement>(controls);
elements.retainAll(validParents);
result.addAll(elements);
return result;
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void updateControlMapping(VControl vControl) {
if (vControl == null) {
return;
}
// delete old mapping
deleteOldMapping(vControl);
// update mapping
final EMFFormsViewContext controlContext = controlContextMap.get(vControl);
final Set<UniqueSetting> map = mappingManager.getAllSettingsFor(vControl.getDomainModelReference(),
controlContext == null ? viewModelContext.getDomainModel() : controlContext.getDomainModel());
if (!controlToSettingMap.containsKey(vControl)) {
// We never add duplicate settings so don't need the overhead of a set (implemented by a map)
controlToSettingMap.put(vControl, new ArrayList<UniqueSetting>(map));
} else {
controlToSettingMap.get(vControl).addAll(map);
}
for (final UniqueSetting setting : map) {
if (!settingToControlMap.containsKey(setting)) {
// There is almost always exactly one control
settingToControlMap.put(setting, new ArrayList<VElement>(1));
handleAddForEObjectMapping(setting);
}
settingToControlMap.get(setting).add(vControl);
if (controlContext != null) {
VElement parentElement = contextParentMap.get(controlContext);
while (parentElement != null) {
settingToControlMap.get(setting).add(parentElement);
final EMFFormsViewContext context = controlContextMap.get(parentElement);
if (context == null) {
break;
}
parentElement = contextParentMap.get(context);
}
}
}
}
private void deleteOldMapping(VControl vControl) {
if (controlToSettingMap.containsKey(vControl)) {
final Set<UniqueSetting> keysWithEmptySets = new LinkedHashSet<UniqueSetting>();
for (final UniqueSetting setting : controlToSettingMap.get(vControl)) {
final List<VElement> controlSet = settingToControlMap.get(setting);
controlSet.remove(vControl); // We never repeat controls in this list
if (controlSet.isEmpty()) {
keysWithEmptySets.add(setting);
}
}
for (final UniqueSetting setting : keysWithEmptySets) {
settingToControlMap.remove(setting);
handleRemoveForEObjectMapping(setting);
}
controlToSettingMap.remove(vControl);
}
}
private void handleAddForEObjectMapping(UniqueSetting setting) {
if (!eObjectToMappedSettings.containsKey(setting.getEObject())) {
eObjectToMappedSettings.put(setting.getEObject(), new LinkedHashSet<UniqueSetting>());
}
eObjectToMappedSettings.get(setting.getEObject()).add(setting);
}
private void handleRemoveForEObjectMapping(UniqueSetting setting) {
final Set<UniqueSetting> settings = eObjectToMappedSettings.get(setting.getEObject());
settings.remove(setting);
if (settings.isEmpty()) {
eObjectToMappedSettings.remove(setting.getEObject());
}
}
/**
* {@inheritDoc}
*/
@Override
public void vControlRemoved(VControl vControl) {
deleteOldMapping(vControl);
final EMFFormsViewContext viewContext = controlContextMap.get(vControl);
if (viewContext != null && contextListenerMap.containsKey(viewContext)) {
contextListenerMap.get(viewContext).removeVControl(vControl);
} else {
dataModelListener.removeVControl(vControl);
}
}
/**
* {@inheritDoc}
*/
@Override
public void vControlAdded(VControl vControl) {
if (vControl.getDomainModelReference() == null) {
return;
}
final EMFFormsViewContext viewContext = controlContextMap.get(vControl);
resolveDMR(vControl, viewContext);
checkAndUpdateSettingToControlMapping(vControl);
if (viewContext != null) {
contextListenerMap.get(viewContext).addVControl(vControl);
} else {
dataModelListener.addVControl(vControl);
}
}
private void resolveDMR(VControl vControl, EMFFormsViewContext childContext) {
/* if child context is null use root context */
final EMFFormsViewContext viewContext = childContext != null ? childContext : viewModelContext;
// FIXME remove
SettingToControlExpandHelper.resolveDomainReferences(
vControl.getDomainModelReference(),
viewContext.getDomainModel(),
viewContext);
}
/**
* {@inheritDoc}
*/
@Override
public void checkAndUpdateSettingToControlMapping(EObject eObject) {
if (VControl.class.isInstance(eObject)) {
final VControl vControl = (VControl) eObject;
if (vControl.getDomainModelReference() == null) {
return;
}
updateControlMapping(vControl);
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emfforms.spi.core.services.view.EMFFormsContextListener#childContextAdded(org.eclipse.emf.ecp.view.spi.model.VElement,
* org.eclipse.emfforms.spi.core.services.view.EMFFormsViewContext)
*/
@Override
public void childContextAdded(VElement parentElement, EMFFormsViewContext childContext) {
childContext.registerViewChangeListener(viewModelChangeListener);
contextParentMap.put(childContext, parentElement);
contextListenerMap.put(childContext, new ViewModelListener(childContext, this));
final TreeIterator<EObject> eAllContents = childContext.getViewModel().eAllContents();
while (eAllContents.hasNext()) {
final EObject next = eAllContents.next();
if (VControl.class.isInstance(next)) {
controlContextMap.put((VControl) next, childContext);
vControlAdded((VControl) next);
}
}
childContext.registerEMFFormsContextListener(this);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emfforms.spi.core.services.view.EMFFormsContextListener#childContextDisposed(org.eclipse.emfforms.spi.core.services.view.EMFFormsViewContext)
*/
@Override
public void childContextDisposed(EMFFormsViewContext childContext) {
if (disposed) {
return;
}
childContext.unregisterViewChangeListener(viewModelChangeListener);
final ViewModelListener listener = contextListenerMap.remove(childContext);
listener.dispose();
final TreeIterator<EObject> eAllContents = childContext.getViewModel().eAllContents();
while (eAllContents.hasNext()) {
final EObject next = eAllContents.next();
if (VControl.class.isInstance(next)) {
final VControl controlToRemove = (VControl) next;
vControlParentsRemoved(childContext, controlToRemove);
vControlRemoved(controlToRemove);
controlContextMap.remove(controlToRemove);
}
}
contextParentMap.remove(childContext);
childContext.unregisterEMFFormsContextListener(this);
}
/**
* Get all settings associated with the given control to remove.
* For every setting, remove its mapping to any parent element of the given child context.
* This is necessary when the child context is removed from this setting to control mapper in order to avoid
* mappings to deleted settings after a child context was removed.
*
* @param childContext The child {@link EMFFormsViewContext} that will be removed from this setting to control
* mapper
* @param controlToRemove The {@link VControl} that will be removed from this setting to control mapper
*/
private void vControlParentsRemoved(EMFFormsViewContext childContext, VControl controlToRemove) {
if (childContext == null) {
return;
}
final VElement parentElement = contextParentMap.get(childContext);
if (parentElement == null) {
return;
}
// remove the mapping of each setting of the removed control to the parent element
for (final UniqueSetting setting : controlToSettingMap.get(controlToRemove)) {
settingToControlMap.get(setting).remove(parentElement);
}
// Then compute the parent of the parent element
final EMFFormsViewContext parentContext = controlContextMap.get(parentElement);
vControlParentsRemoved(parentContext, controlToRemove);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emfforms.spi.core.services.view.EMFFormsContextListener#contextInitialised()
*/
@Override
public void contextInitialised() {
// do nothing
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emfforms.spi.core.services.view.EMFFormsContextListener#contextDispose()
*/
@Override
public void contextDispose() {
viewModelContext.unregisterEMFFormsContextListener(this);
dataModelListener.dispose();
settingToControlMap.clear();
controlContextMap.clear();
contextParentMap.clear();
contextListenerMap.clear();
disposed = true;
}
@Override
public boolean hasControlsFor(EObject eObject) {
if (eObjectToMappedSettings.containsKey(eObject)) {
return true;
}
return false;
}
@Override
public Collection<EObject> getEObjectsWithSettings() {
final Set<EObject> result = new LinkedHashSet<EObject>();
result.addAll(eObjectToMappedSettings.keySet());
return result;
}
@Override
public Set<UniqueSetting> getSettingsForControl(VControl control) {
final List<UniqueSetting> settings = controlToSettingMap.get(control);
if (settings == null) {
return Collections.emptySet();
}
return new SetView<UniqueSetting>(settings);
}
/**
* A view of any collection as an unmodifiable set.
*
* @param <E> the element type of the collection
*/
final class SetView<E> extends AbstractSet<E> {
private final Collection<? extends E> content;
/**
* Initializes me with my {@code content}.
*
* @param content the elements that I contain
*/
SetView(Collection<? extends E> content) {
super();
this.content = content;
}
@Override
public Iterator<E> iterator() {
final Iterator<? extends E> delegate = content.iterator();
return new Iterator<E>() {
@Override
public boolean hasNext() {
return delegate.hasNext();
}
@Override
public E next() {
return delegate.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public int size() {
return content.size();
}
}
}