blob: 2d33c23b699371c542c283c977807583bc52997a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2015 THALES GLOBAL SERVICES 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.tree.business.internal.dialect.common.tree;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.sirius.ext.base.Option;
import org.eclipse.sirius.ext.base.Options;
import org.eclipse.sirius.synchronizer.AutomaticCreator;
import org.eclipse.sirius.synchronizer.ChildCreationSupport;
import org.eclipse.sirius.synchronizer.CreatedOutput;
import org.eclipse.sirius.synchronizer.IntegerProvider;
import org.eclipse.sirius.synchronizer.Mapping;
import org.eclipse.sirius.synchronizer.MappingHiearchy;
import org.eclipse.sirius.synchronizer.MappingHiearchyTable;
import org.eclipse.sirius.synchronizer.ModelToModelSynchronizer;
import org.eclipse.sirius.synchronizer.OutputDescriptor;
import org.eclipse.sirius.synchronizer.PreRefreshStatus;
import org.eclipse.sirius.synchronizer.SemanticPartition;
import org.eclipse.sirius.synchronizer.SemanticPartitionInvalidator;
import org.eclipse.sirius.synchronizer.SemanticPartitions;
import org.eclipse.sirius.synchronizer.Signature;
import org.eclipse.sirius.synchronizer.SignatureProvider;
import org.eclipse.sirius.synchronizer.StringSignature;
import org.eclipse.sirius.tree.DTree;
import org.eclipse.sirius.tree.DTreeItem;
import org.eclipse.sirius.tree.DTreeItemContainer;
import org.eclipse.sirius.tree.TreeFactory;
import org.eclipse.sirius.tree.TreeItemStyle;
import org.eclipse.sirius.tree.business.internal.dialect.common.viewpoint.GlobalContext;
import org.eclipse.sirius.tree.business.internal.dialect.common.viewpoint.MappingBasedPartition;
import org.eclipse.sirius.tree.business.internal.refresh.DTreeElementSynchronizerSpec;
import org.eclipse.sirius.tree.description.TreeDescription;
import org.eclipse.sirius.tree.description.TreeItemMapping;
import org.eclipse.sirius.tree.tools.internal.Messages;
import org.eclipse.sirius.viewpoint.RGBValues;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.BiMap;
import com.google.common.collect.Collections2;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultiset;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Ordering;
/**
* Update the {@link DTree} model according to the semantic model and the
* {@link TreeDescription} model.
*
* @author cbrun
*/
public class DTreeRefresh {
private DTreeItemContainer container;
private SemanticPartitionInvalidator invalidator;
private Iterable<? extends TreeItemMapping> mappings;
private GlobalContext ctx;
/**
* Creates a new DTreeRefresh.
*
* @param tree
* The tree to refresh
* @param mappings
* the item mappings
* @param invalidator
* the {@link SemanticPartitionInvalidator}
* @param ctx
* the global context
*/
public DTreeRefresh(DTreeItemContainer tree, Iterable<? extends TreeItemMapping> mappings, SemanticPartitionInvalidator invalidator, GlobalContext ctx) {
this.container = tree;
this.invalidator = invalidator;
this.ctx = ctx;
this.mappings = mappings;
}
/**
* Refreshes the tree lazily, i.e. does not refresh sub tree of collapsed
* {@link DTreeItemContainer}.
*
* @param monitor
* a {@link IProgressMonitor} to use
*/
public void refresh(IProgressMonitor monitor) {
refresh(false, monitor);
}
/**
* Refreshes the tree.
*
* @param fullRefresh
* true to do a full refresh of {@link DTreeItemContainer} even
* sub tree of collapsed {@link DTreeItemContainer}
*
* @param monitor
* a {@link IProgressMonitor} to use
*/
public void refresh(boolean fullRefresh, IProgressMonitor monitor) {
try {
monitor.beginTask(Messages.DTreeUserInteraction_treeRefresh, 8);
if (ctx.getModelAccessor().getPermissionAuthority().canEditInstance(container)) {
MappingHiearchyTable hierarchy = new MappingHiearchyTable();
SignatureProvider signProvider = new TreeSignatureProvider(hierarchy);
SemanticPartitionProvider semProvider = new SemanticPartitionProvider(this.ctx);
final TreeMappingProvider provider = new TreeMappingProvider(semProvider);
Iterable<Mapping> providedMappings = Iterables.transform(mappings, new Function<TreeItemMapping, Mapping>() {
@Override
public Mapping apply(TreeItemMapping from) {
return provider.getOrCreate(from);
}
});
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
monitor.worked(1);
hierarchy.compute(Lists.newArrayList(providedMappings));
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
monitor.worked(1);
DTreePreRefreshStatus pre = new DTreePreRefreshStatus(ctx, provider);
ModelToModelSynchronizer refresher = new ModelToModelSynchronizer(this.invalidator, hierarchy, pre, signProvider);
CreatedOutput cDiag = buildOutput(provider);
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
monitor.worked(1);
refresher.update(cDiag, fullRefresh, new SubProgressMonitor(monitor, 5));
}
} finally {
monitor.done();
}
}
private CreatedOutput buildOutput(final TreeMappingProvider provider) {
if (container instanceof DTree) {
DTree tree = (DTree) container;
OutputDTreeDescriptor existingDTree = new OutputDTreeDescriptor(tree.getTarget(), tree.getDescription(), provider);
CreatedDTree cDiag = new CreatedDTree(ctx, tree, existingDTree);
return cDiag;
} else if (container instanceof DTreeItem) {
DTreeItem item = (DTreeItem) container;
DTreeItemContainer treeItemcontainer = item.getContainer();
OutputTreeItemDescriptor descriptor = new OutputTreeItemDescriptor(treeItemcontainer, item.getTarget(), item.getActualMapping(), 0, provider);
CreatedTreeItem created = new CreatedTreeItem(ctx, item, descriptor);
return created;
}
throw new RuntimeException(Messages.DTreeRefresh_unknownRepresentationContainer);
}
}
class DTreePreRefreshStatus implements PreRefreshStatus {
private TreeMappingProvider provider;
private Option<List<CreatedOutput>> computedOutputs;
private GlobalContext ctx;
public DTreePreRefreshStatus(GlobalContext ctx, TreeMappingProvider provider) {
this.provider = provider;
computedOutputs = Options.newNone();
this.ctx = ctx;
}
@Override
public Iterable<CreatedOutput> getExistingOutputs() {
if (computedOutputs.some()) {
return computedOutputs.get();
}
return Lists.newArrayList();
}
@Override
public void computeStatus(CreatedOutput container, Collection<? extends Mapping> childMappings) {
List<CreatedOutput> result = Lists.newArrayList();
int i = 0;
DTreeItemContainer viewContainer = (DTreeItemContainer) container.getCreatedElement();
Iterator<DTreeItem> it = viewContainer.getOwnedTreeItems().iterator();
while (it.hasNext()) {
DTreeItem cur = it.next();
/*
* we should create the output
*/
OutputTreeItemDescriptor descriptor = new OutputTreeItemDescriptor((DTreeItemContainer) container.getCreatedElement(), cur.getTarget(), cur.getActualMapping(), i, provider);
CreatedTreeItem newOuput = new CreatedTreeItem(ctx, cur, descriptor);
result.add(newOuput);
i++;
}
computedOutputs = Options.newSome(result);
}
}
class TreeMappingProvider {
protected BiMap<EObject, Mapping> mappingToRMapping = HashBiMap.create();
private SemanticPartitionProvider semProvider;
public TreeMappingProvider(SemanticPartitionProvider semProvider) {
this.semProvider = semProvider;
}
public RTreeItemMapping getOrCreate(TreeItemMapping mapping) {
if (mappingToRMapping.get(mapping) != null) {
return (RTreeItemMapping) mappingToRMapping.get(mapping);
}
RTreeItemMapping newOne = new RTreeItemMapping(mapping, this);
mappingToRMapping.put(mapping, newOne);
return newOne;
}
public RTreeMapping getOrCreate(TreeDescription mapping) {
if (mappingToRMapping.get(mapping) != null) {
return (RTreeMapping) mappingToRMapping.get(mapping);
}
RTreeMapping newOne = new RTreeMapping(mapping, this);
mappingToRMapping.put(mapping, newOne);
return newOne;
}
public SemanticPartitionProvider getSemanticProvider() {
return semProvider;
}
}
class SemanticPartitionProvider {
private GlobalContext ctx;
public SemanticPartitionProvider(GlobalContext ctx) {
this.ctx = ctx;
}
public SemanticPartition getSemanticPartition(TreeItemMapping nm) {
return new MappingBasedPartition(ctx, nm.getDomainClass(), Options.newSome(nm.getSemanticCandidatesExpression()), Options.newSome(nm));
}
}
class RTreeMapping implements Mapping {
private TreeDescription treeDescription;
private TreeMappingProvider provider;
private SemanticPartition semPartition;
public RTreeMapping(TreeDescription description, TreeMappingProvider provider) {
this.treeDescription = description;
this.provider = provider;
this.semPartition = SemanticPartitions.eAllContents(description.getDomainClass());
}
@Override
public Option<Mapping> getSuper() {
return Options.newNone();
}
@Override
public SemanticPartition getSemanticPartition() {
return semPartition;
}
public List<Mapping> getChildMappings() {
List<Mapping> result = Lists.newArrayList();
for (TreeItemMapping mapping : treeDescription.getSubItemMappings()) {
result.add(provider.getOrCreate(mapping));
}
return result;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public Option<AutomaticCreator> getCreator() {
return Options.newNone();
}
}
class CreatedTreeItem extends AbstractCreatedDTreeItemContainer {
private DTreeItem tItem;
private OutputTreeItemDescriptor descriptor;
private int newIndex;
private Option<Mapping> newMapping = Options.newNone();
public CreatedTreeItem(GlobalContext ctx, DTreeItem tItem, OutputTreeItemDescriptor descriptor) {
super(ctx);
this.tItem = tItem;
this.descriptor = descriptor;
this.newIndex = descriptor.getIndex();
}
@Override
public OutputTreeItemDescriptor getDescriptor() {
return descriptor;
}
@Override
public void setNewIndex(int nextIndex) {
this.newIndex = nextIndex;
}
@Override
public int getNewIndex() {
return this.newIndex;
}
@Override
public EObject getCreatedElement() {
return tItem;
}
@Override
public void updateMapping() {
if (newMapping.some()) {
tItem.setActualMapping(((RTreeItemMapping) newMapping.get()).getDescription());
} else {
throw new RuntimeException(Messages.DTreeRefresh_noMapping);
}
}
@Override
public void refresh() {
DTreeElementSynchronizerSpec sync = new DTreeElementSynchronizerSpec(getGlobalContext().getInterpreter(), getGlobalContext().getModelAccessor());
sync.refresh(tItem);
}
@Override
public void setNewMapping(Mapping map) {
newMapping = Options.newSome(map);
}
@Override
public Option<? extends ChildCreationSupport> getChildSupport() {
return Options.newSome(new TreeItemContainerChildSupport(getGlobalContext(), tItem));
}
/**
* Synchronize direct children only if the current {@link DTreeItem} is
* expanded.
*/
@Override
public boolean synchronizeChildren() {
return tItem.isExpanded();
}
@Override
public List<Mapping> getChildMappings() {
return getDescriptor().getMapping().getChildMappings();
}
}
class TreeItemContainerChildSupport implements ChildCreationSupport {
private DTreeItemContainer container;
private GlobalContext ctx;
public TreeItemContainerChildSupport(GlobalContext ctx, DTreeItemContainer container) {
this.container = container;
this.ctx = ctx;
}
@Override
public void reorderChilds(Iterable<CreatedOutput> outDesc) {
final Multiset<TreeItemMapping> subMappings = LinkedHashMultiset.create();
Set<TreeItemMapping> mappings = new HashSet<TreeItemMapping>();
final Map<EObject, CreatedOutput> outputToItem = Maps.newHashMap();
for (CreatedOutput createdOutput : outDesc) {
EObject createdElement = createdOutput.getCreatedElement();
outputToItem.put(createdElement, createdOutput);
if (createdElement instanceof DTreeItem) {
DTreeItem createdDTreeItem = (DTreeItem) createdElement;
TreeItemMapping actualMapping = createdDTreeItem.getActualMapping();
subMappings.add(actualMapping);
mappings.add(actualMapping);
}
}
// Does not need to sort DTreeItem according to their mapping if there
// is only one mapping
if (mappings.size() > 1) {
// Counts subMappings to correctly sort tree items regarding mapping
// order (items have been created regarding the semantic candidates
// order)
int startIndex = 0;
final Map<TreeItemMapping, Integer> startIndexes = Maps.newHashMap();
for (TreeItemMapping itemMapping : subMappings) {
startIndexes.put(itemMapping, startIndex);
startIndex += subMappings.count(itemMapping);
}
Function<DTreeItem, Integer> getNewIndex = new Function<DTreeItem, Integer>() {
@Override
public Integer apply(DTreeItem from) {
// init with element count : elements with unknown mapping
// will
// be placed at
// the end.
int index = outputToItem.size();
TreeItemMapping itemMapping = from.getActualMapping();
if (itemMapping != null && startIndexes.containsKey(itemMapping)) {
index = startIndexes.get(itemMapping);
}
CreatedOutput createdOutput = outputToItem.get(from);
if (createdOutput != null) {
return index + createdOutput.getNewIndex();
}
return -1;
}
};
ECollections.sort(container.getOwnedTreeItems(), Ordering.natural().onResultOf(getNewIndex));
}
}
@Override
public void deleteChild(CreatedOutput outDesc) {
/*
* The cross referencer is actually optional for the eDelete method of
* the model accessor.
*/
ECrossReferenceAdapter xRef = ECrossReferenceAdapter.getCrossReferenceAdapter(container);
ctx.getModelAccessor().eDelete(outDesc.getCreatedElement(), xRef);
}
@Override
public CreatedOutput createChild(OutputDescriptor outDesc) {
OutputTreeItemDescriptor descriptor = (OutputTreeItemDescriptor) outDesc;
DTreeItem dTreeItem = TreeFactory.eINSTANCE.createDTreeItem();
dTreeItem.setActualMapping(descriptor.getMapping().getDescription());
dTreeItem.setTarget(outDesc.getSourceElement());
dTreeItem.getSemanticElements().add(outDesc.getSourceElement());
TreeItemStyle treeItemStyle = TreeFactory.eINSTANCE.createTreeItemStyle();
RGBValues backgroundColor = RGBValues.DEFAULT_GRAY;
RGBValues labelColor = RGBValues.DEFAULT_GRAY;
treeItemStyle.setBackgroundColor(backgroundColor);
treeItemStyle.setLabelColor(labelColor);
dTreeItem.setOwnedStyle(treeItemStyle);
container.getOwnedTreeItems().add(dTreeItem);
CreatedTreeItem newOne = new CreatedTreeItem(ctx, dTreeItem, descriptor);
return newOne;
}
}
abstract class AbstractCreatedDTreeItemContainer implements CreatedOutput {
private GlobalContext ctx;
public AbstractCreatedDTreeItemContainer(GlobalContext ctx) {
this.ctx = ctx;
}
public GlobalContext getGlobalContext() {
return ctx;
}
}
class CreatedDTree extends AbstractCreatedDTreeItemContainer {
private DTree dnode;
private OutputDescriptor descriptor;
private int newIndex;
public CreatedDTree(GlobalContext ctx, DTree tree, OutputDescriptor descriptor) {
super(ctx);
this.dnode = tree;
this.descriptor = descriptor;
this.newIndex = descriptor.getIndex();
}
@Override
public OutputDescriptor getDescriptor() {
return descriptor;
}
@Override
public void setNewIndex(int nextIndex) {
throw new IllegalArgumentException(Messages.DTreeRefresh_nonsense);
}
@Override
public EObject getCreatedElement() {
return dnode;
}
@Override
public void updateMapping() {
throw new IllegalArgumentException(Messages.DTreeRefresh_nonsense);
}
@Override
public void refresh() {
/*
* here we should update any DTree info computed from semantic elements.
* So far nothing...
*/
}
@Override
public void setNewMapping(Mapping map) {
throw new IllegalArgumentException(Messages.DTreeRefresh_nonsense);
}
@Override
public Option<? extends ChildCreationSupport> getChildSupport() {
return Options.newSome(new TreeItemContainerChildSupport(getGlobalContext(), dnode));
}
/**
* Always synchronize direct children of a {@link DTree} as it has not the
* capability of collapse as {@link fr.obeo.dsl.viewpoint.tree.DTreeItem}.
*/
@Override
public boolean synchronizeChildren() {
return true;
}
@Override
public List<Mapping> getChildMappings() {
return ((RTreeMapping) getDescriptor().getMapping()).getChildMappings();
}
@Override
public int getNewIndex() {
return newIndex;
}
}
class TreeItemCreator implements AutomaticCreator {
private TreeItemMapping nm;
private TreeMappingProvider provider;
public TreeItemCreator(TreeItemMapping nm, TreeMappingProvider provider) {
super();
this.nm = nm;
this.provider = provider;
}
@Override
public Collection<? extends OutputDescriptor> computeDescriptors(final CreatedOutput context, Iterator<EObject> it) {
final AbstractCreatedDTreeItemContainer castedContext = (AbstractCreatedDTreeItemContainer) context;
Predicate<OutputTreeItemDescriptor> filterPredicates = new Predicate<OutputTreeItemDescriptor>() {
@Override
public boolean apply(OutputTreeItemDescriptor input) {
return new TreeItemMappingExpression(castedContext.getGlobalContext(), input.getMapping().getDescription()).checkPrecondition(input.getSourceElement(), input.getViewContainer());
}
};
List<OutputTreeItemDescriptor> outputs = Lists.newArrayList();
int i = 0;
while (it.hasNext()) {
EObject from = it.next();
outputs.add(new OutputTreeItemDescriptor((DTreeItemContainer) context.getCreatedElement(), from, this.nm, i, this.provider));
i++;
}
return Collections2.filter(outputs, filterPredicates);
}
};
class RTreeItemMapping implements Mapping {
private TreeItemMapping nm;
private TreeMappingProvider provider;
private AutomaticCreator creator;
public RTreeItemMapping(TreeItemMapping mapping, TreeMappingProvider provider) {
this.nm = mapping;
this.provider = provider;
this.creator = new TreeItemCreator(nm, provider);
}
@Override
public Option<? extends Mapping> getSuper() {
if (nm.getSpecialize() != null) {
return Options.newSome(provider.getOrCreate(nm.getSpecialize()));
}
return Options.newNone();
}
@Override
public SemanticPartition getSemanticPartition() {
return provider.getSemanticProvider().getSemanticPartition(nm);
}
public List<Mapping> getChildMappings() {
List<Mapping> result = Lists.newArrayList();
result.addAll(Collections2.transform(nm.getAllSubMappings(), new Function<TreeItemMapping, RTreeItemMapping>() {
@Override
public RTreeItemMapping apply(TreeItemMapping from) {
return provider.getOrCreate(from);
}
}));
return result;
}
@Override
public boolean isEnabled() {
return true;
}
public TreeItemMapping getDescription() {
return nm;
}
@Override
public Option<AutomaticCreator> getCreator() {
return Options.newSome(creator);
}
}
class TreeSignatureProvider implements SignatureProvider {
private Map<String, Signature> allSignatures = Maps.newHashMap();
private MappingHiearchyTable hierarchyTable;
public TreeSignatureProvider(MappingHiearchyTable hierarchyTable) {
this.hierarchyTable = hierarchyTable;
}
@Override
public Signature getSignature(OutputDescriptor descriptor) {
if (descriptor instanceof OutputTreeItemDescriptor) {
return doGetSignature((OutputTreeItemDescriptor) descriptor);
} else if (descriptor instanceof OutputDTreeDescriptor) {
return doGetSignature((OutputDTreeDescriptor) descriptor);
}
throw new RuntimeException(MessageFormat.format(Messages.DTreeRefresh_unknownDescriptor, descriptor));
}
private Signature doGetSignature(OutputDTreeDescriptor desc) {
return getOrCreate("tree " + getURI(desc.getSourceElement()) + desc.getMapping().toString()); //$NON-NLS-1$
}
private Signature getOrCreate(String string) {
Signature existing = allSignatures.get(string);
if (existing == null) {
existing = new StringSignature(string);
allSignatures.put(string, existing);
}
return existing;
}
private String getURI(EObject sourceElement) {
return EcoreUtil.getURI(sourceElement).toString();
}
private Signature doGetSignature(OutputTreeItemDescriptor desc) {
String sourceID = desc.getSourceElement() == null ? String.valueOf(desc.hashCode()) : IntegerProvider.getInteger(desc.getSourceElement()).toString();
String containerID = IntegerProvider.getInteger(desc.getViewContainer()).toString();
Collection<MappingHiearchy> hierarchy = hierarchyTable.getHierarchy(desc.getMapping());
return getOrCreate(hierarchy + sourceID + containerID);
}
}
class OutputDTreeDescriptor implements OutputDescriptor {
private EObject from;
private TreeDescription mapping;
private TreeMappingProvider provider;
public OutputDTreeDescriptor(EObject from, TreeDescription nm, TreeMappingProvider provider) {
this.from = from;
this.mapping = nm;
this.provider = provider;
}
@Override
public int getIndex() {
return 0;
}
@Override
public EObject getSourceElement() {
return from;
}
@Override
public Mapping getMapping() {
return provider.getOrCreate(mapping);
}
public boolean isSame(OutputDescriptor other) {
return false;
}
}
class OutputTreeItemDescriptor implements OutputDescriptor {
private TreeItemMapping mapping;
private EObject from;
private int position;
private TreeMappingProvider provider;
private DTreeItemContainer container;
public OutputTreeItemDescriptor(DTreeItemContainer container, EObject from, TreeItemMapping nm, int position, TreeMappingProvider provider) {
this.from = from;
this.mapping = nm;
this.position = position;
this.provider = provider;
this.container = container;
}
public DTreeItemContainer getViewContainer() {
return container;
}
@Override
public int getIndex() {
return position;
}
@Override
public EObject getSourceElement() {
return from;
}
@Override
public RTreeItemMapping getMapping() {
return provider.getOrCreate(mapping);
}
}