/****************************************************************************** * Copyright (c) 2016 Oracle * 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: * Shenxue Zhou - initial implementation and ongoing maintenance * Konstantin Komissarchik - [342775] Support EL in MasterDetailsTreeNodeDef.ImagePath * Konstantin Komissarchik - [374154] IllegalStateException in ServiceContext when disposing diagram connection templates * Konstantin Komissarchik - [378756] Convert ModelElementListener and ModelPropertyListener to common listener infrastructure ******************************************************************************/ package org.eclipse.sapphire.ui.diagram.internal; import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import org.eclipse.sapphire.Element; import org.eclipse.sapphire.ElementList; import org.eclipse.sapphire.FilteredListener; import org.eclipse.sapphire.ListProperty; import org.eclipse.sapphire.Listener; import org.eclipse.sapphire.PropertyEvent; import org.eclipse.sapphire.modeling.el.FunctionResult; import org.eclipse.sapphire.ui.diagram.ConnectionAddEvent; import org.eclipse.sapphire.ui.diagram.ConnectionDeleteEvent; import org.eclipse.sapphire.ui.diagram.def.IDiagramConnectionDef; import org.eclipse.sapphire.ui.diagram.def.IDiagramImplicitConnectionBindingDef; import org.eclipse.sapphire.ui.diagram.def.IModelElementTypeDef; import org.eclipse.sapphire.ui.diagram.editor.DiagramNodePart; import org.eclipse.sapphire.ui.diagram.editor.DiagramNodeTemplate; import org.eclipse.sapphire.ui.diagram.editor.DiagramNodeTemplate.DiagramNodeTemplateListener; import org.eclipse.sapphire.ui.diagram.editor.SapphireDiagramEditorPagePart; /** * @author <a href="mailto:shenxue.zhou@oracle.com">Shenxue Zhou</a> * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public class DiagramImplicitConnectionTemplate extends DiagramConnectionTemplate { public static abstract class DiagramImplicitConnectionTemplateListener { public void handleConnectionAddEvent(final ConnectionAddEvent event) { } public void handleConnectionDeleteEvent(final ConnectionDeleteEvent event) { } } private IDiagramImplicitConnectionBindingDef bindingDef; private IDiagramConnectionDef connectionDef; private SapphireDiagramEditorPagePart diagramEditor; private String propertyName; // private ModelPath allDescendentsPath; private ListProperty modelProperty; private List<Class<?>> modelElementTypes; private List<StandardImplicitConnectionPart> implicitConnections; private Listener modelPropertyListener; private Set<DiagramImplicitConnectionTemplateListener> templateListeners; private Map<Element, FunctionResult> listEntryFunctionMap; public DiagramImplicitConnectionTemplate(IDiagramImplicitConnectionBindingDef bindingDef) { this.bindingDef = bindingDef; } @Override public void init() { this.diagramEditor = (SapphireDiagramEditorPagePart)parent(); this.listEntryFunctionMap = new IdentityHashMap<Element, FunctionResult>(); this.connectionDef = (IDiagramConnectionDef)super.definition(); this.propertyName = this.bindingDef.getProperty().content(); this.modelProperty = (ListProperty)getModelElement().property(this.propertyName).definition(); this.modelElementTypes = new ArrayList<Class<?>>(); ElementList<IModelElementTypeDef> types = this.bindingDef.getModelElementTypes(); for (IModelElementTypeDef typeDef : types) { this.modelElementTypes.add((Class<?>)typeDef.getType().target().artifact()); } initImplicitConnectionParts(); this.templateListeners = new CopyOnWriteArraySet<DiagramImplicitConnectionTemplateListener>(); // Add model property listener this.modelPropertyListener = new FilteredListener<PropertyEvent>() { @Override protected void handleTypedEvent( final PropertyEvent event ) { refreshImplicitConnections(); } }; addModelListener(); // Add node template listener. When the xml file is editing in the source // tab or in external editor, we have no control over who receives model events // first. Sometimes the model listener here is notified before the model listener // in diagram node template is notified. In this case, dangling connection parts // are created before node parts are created. So when new node part is created, we // need to notify connection template. List<DiagramNodeTemplate> nodeTemplates = this.diagramEditor.getNodeTemplates(this.modelProperty); if (!nodeTemplates.isEmpty()) { NodeTemplateListener nodeTemplateListener = new NodeTemplateListener(); for (DiagramNodeTemplate nodeTemplate : nodeTemplates) { nodeTemplate.addTemplateListener(nodeTemplateListener); } } } public SapphireDiagramEditorPagePart getDiagramEditorPart() { return this.diagramEditor; } @Override public void addModelListener() { getModelElement().attach(this.modelPropertyListener, this.propertyName); // I don't think the following code is necessary since the condition expression // refreshes implicit connections. TODO more testing when the "Property instance construct" // change propogrates to OEPE // String temp = this.propertyName + "/*"; // this.allDescendentsPath = new ModelPath(temp); // getModelElement().attach(this.modelPropertyListener, this.allDescendentsPath); } @Override public void removeModelListener() { getModelElement().detach(this.modelPropertyListener, this.propertyName); // getModelElement().detach(this.modelPropertyListener, this.allDescendentsPath); } public void refreshImplicitConnections() { List<Element> newFilteredList = getFilteredModelElementList(); for (StandardImplicitConnectionPart connPart : this.implicitConnections) { notifyConnectionDelete(connPart); connPart.dispose(); } this.implicitConnections.clear(); for (int i = 0; i < newFilteredList.size() - 1; i++) { DiagramNodePart srcNode = this.diagramEditor.getDiagramNodePart(newFilteredList.get(i)); DiagramNodePart targetNode = this.diagramEditor.getDiagramNodePart(newFilteredList.get(i+1)); if (srcNode != null && srcNode.getDiagramNodeTemplate().visible() && targetNode != null && targetNode.getDiagramNodeTemplate().visible()) { StandardImplicitConnectionPart connPart = createNewImplicitConnectionPart(newFilteredList.get(i), newFilteredList.get(i+1)); this.implicitConnections.add(connPart); notifyConnectionAdd(connPart); } } } public List<StandardImplicitConnectionPart> getImplicitConnections() { return this.implicitConnections; } private void initImplicitConnectionParts() { List<Element> newFilteredList = getFilteredModelElementList(); this.implicitConnections = new ArrayList<StandardImplicitConnectionPart>(); for (int i = 0; i < newFilteredList.size() - 1; i++) { StandardImplicitConnectionPart connPart = createNewImplicitConnectionPart(newFilteredList.get(i), newFilteredList.get(i+1)); this.implicitConnections.add(connPart); } } private List<Element> getFilteredModelElementList() { ElementList<?> list = getModelElement().property(this.modelProperty); List<Element> filteredList = new ArrayList<Element>(); for( Element listEntryModelElement : list ) { if (isRightEntry(listEntryModelElement)) { filteredList.add(listEntryModelElement); } } return filteredList; } private StandardImplicitConnectionPart createNewImplicitConnectionPart(Element srcNodeModel, Element targetNodeModel) { StandardImplicitConnectionPart connPart = new StandardImplicitConnectionPart(srcNodeModel, targetNodeModel); connPart.init(this, srcNodeModel, this.connectionDef, Collections.<String,String>emptyMap()); connPart.initialize(); return connPart; } private boolean isRightEntry(Element entryModelElement) { boolean isRightType = true; if (this.modelElementTypes.size() > 0) { isRightType = false; for (Class<?> eleType : this.modelElementTypes) { if (eleType.isAssignableFrom(entryModelElement.getClass())) { isRightType = true; break; } } } if (isRightType && this.bindingDef.getCondition() != null) { isRightType = false; // apply the condition FunctionResult fr = this.listEntryFunctionMap.get(entryModelElement); if (fr == null) { fr = initExpression ( entryModelElement, this.bindingDef.getCondition().content(), String.class, null, new Runnable() { public void run() { refreshImplicitConnections(); } } ); this.listEntryFunctionMap.put(entryModelElement, fr); } if (fr != null && ((String)fr.value()).equals("true")) { isRightType = true; } } return isRightType; } public void addTemplateListener( final DiagramImplicitConnectionTemplateListener listener ) { this.templateListeners.add( listener ); } public void removeTemplateListener( final DiagramImplicitConnectionTemplateListener listener ) { this.templateListeners.remove( listener ); } public void notifyConnectionAdd(StandardImplicitConnectionPart connPart) { for( DiagramImplicitConnectionTemplateListener listener : this.templateListeners ) { listener.handleConnectionAddEvent(new ConnectionAddEvent(connPart)); } } public void notifyConnectionDelete(StandardImplicitConnectionPart connPart) { for( DiagramImplicitConnectionTemplateListener listener : this.templateListeners ) { listener.handleConnectionDeleteEvent(new ConnectionDeleteEvent(connPart)); } } @Override public void dispose() { for( FunctionResult fr : this.listEntryFunctionMap.values() ) { if( fr != null ) { fr.dispose(); } } List<StandardImplicitConnectionPart> connParts = getImplicitConnections(); for (StandardImplicitConnectionPart connPart : connParts) { connPart.dispose(); } } private class NodeTemplateListener extends DiagramNodeTemplateListener { @Override public void handleNodeAdd(final DiagramNodePart nodePart) { refreshImplicitConnections(); } } }