/* * Copyright (c) 2013, IETR/INSA of Rennes * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the IETR/INSA of Rennes nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ package net.sf.orcc.xdf.ui.features; import java.util.ArrayList; import java.util.List; import net.sf.orcc.df.Argument; import net.sf.orcc.df.Instance; import net.sf.orcc.df.Network; import net.sf.orcc.df.Port; import net.sf.orcc.graph.Vertex; import net.sf.orcc.util.OrccLogger; import net.sf.orcc.util.OrccUtil; import net.sf.orcc.xdf.ui.diagram.OrccDiagramTypeProvider; import net.sf.orcc.xdf.ui.diagram.XdfDiagramFeatureProvider; import net.sf.orcc.xdf.ui.util.PropsUtil; import net.sf.orcc.xdf.ui.util.XdfUtil; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Path; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.graphiti.features.IFeatureProvider; import org.eclipse.graphiti.features.context.IAddConnectionContext; import org.eclipse.graphiti.features.context.IContext; import org.eclipse.graphiti.features.context.IUpdateContext; import org.eclipse.graphiti.features.context.impl.AddContext; import org.eclipse.graphiti.features.context.impl.CustomContext; import org.eclipse.graphiti.features.custom.ICustomFeature; import org.eclipse.graphiti.features.impl.DefaultUpdateDiagramFeature; import org.eclipse.graphiti.mm.Property; import org.eclipse.graphiti.mm.algorithms.styles.Style; import org.eclipse.graphiti.mm.algorithms.styles.StylesPackage; import org.eclipse.graphiti.mm.pictograms.Diagram; import org.eclipse.graphiti.mm.pictograms.PictogramElement; import org.eclipse.graphiti.mm.pictograms.Shape; import org.eclipse.graphiti.services.Graphiti; import org.eclipse.graphiti.ui.editor.DiagramBehavior; import org.eclipse.jface.dialogs.MessageDialog; /** * This feature try to detect cases when a Diagram or a Network need to be * updated according to data contained from other one. It is executed each time * a Diagram is opened, thanks to the result of isAutoUpdateAtStartup(). * * This feature only apply on a Diagram. For updates on sub-shapes (instances, * ports, etc.) please see updates method in the corresponding patterns. * * @see OrccDiagramTypeProvider#isAutoUpdateAtStartup() * @author Antoine Lorence * */ public class UpdateDiagramFeature extends DefaultUpdateDiagramFeature { private static String GLOBAL_VERSION_KEY = "xdf_diagram_version"; private static int VERSION_1 = 1; private static int CURRENT_EDITOR_VERSION = 2; private boolean hasDoneChanges; public UpdateDiagramFeature(IFeatureProvider fp) { super(fp); hasDoneChanges = false; } @Override public boolean hasDoneChanges() { return hasDoneChanges; } @Override public boolean update(IUpdateContext context) { if (!(context.getPictogramElement() instanceof Diagram)) { OrccLogger.debugln("UpdateDiagramFeature has been used with a non Diagram parameter: " + context.getPictogramElement().getClass().toString()); return false; } final EditingDomain editingDomain = getDiagramBehavior().getEditingDomain(); final ResourceSet resourceSet = editingDomain.getResourceSet(); final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); final Diagram diagram = (Diagram) context.getPictogramElement(); final Object linkedBo = getBusinessObjectForPictogramElement(diagram); final URI diagramUri = diagram.eResource().getURI(); final URI xdfUri = diagramUri.trimFileExtension().appendFileExtension( OrccUtil.NETWORK_SUFFIX); final Network network; if (linkedBo == null || !(linkedBo instanceof Network)) { if (root.exists(new Path(xdfUri.toPlatformString(true)))) { network = (Network) resourceSet.getResource(xdfUri, true).getContents().get(0); link(diagram, network); } else { MessageDialog.openWarning(XdfUtil.getDefaultShell(), "Warning", "This diagram has no network attached. It should not be used."); return false; } } else { network = (Network) linkedBo; final Resource linkedRes = network.eResource(); if (linkedRes == null || !linkedRes.getURI().equals(xdfUri)) { // Particular case. The existing network is not contained in a // resource, or its resource has a wrong URI. It can happen if // the diagram has been moved or duplicated without the // corresponding xdf. The Move/Rename participant should take // care of that. final Resource res = editingDomain.createResource(xdfUri.toString()); res.getContents().add(EcoreUtil.copy(network)); hasDoneChanges = true; } } // Check network validity / initialize diagram. if (diagram.getChildren().size() == 0 && network.getChildren().size() > 0) { // Diagram is new / empty. We check network validity final List<String> updatedNetworkWarnings = new ArrayList<String>(); hasDoneChanges |= fixNetwork(network, updatedNetworkWarnings); // Initialize the new diagram hasDoneChanges |= initializeDiagramFromNetwork(network, diagram); // Display a synthesis message to user, to tell him what have been // automatically modified in the diagram/network he just opened if (updatedNetworkWarnings.size() > 0) { final StringBuilder message = new StringBuilder( "The network has been automatically updated:"); message.append('\n'); for (final String msg : updatedNetworkWarnings) { message.append(msg).append('\n'); } MessageDialog.openInformation(XdfUtil.getDefaultShell(), "Network update", message.toString()); } } else { checkDiagramValidity(getDiagram()); } updateVersion(diagram); return hasDoneChanges; } /** * Check the given network to detect potential issue, and fix them. For each * fixed issue, a message is appended to the given messagesList. * * @param network * @param messagesList * @return true if something has been modified in the network */ public boolean fixNetwork(final Network network, final List<String> messagesList) { // In this list, we will store all invalid objects which needs to be // deleted final List<EObject> toDelete = new ArrayList<EObject>(); // Check for null started or terminated connections. This can happen if // a port, an instance or an instance port has been renamed from outside for(final net.sf.orcc.df.Connection connection : network.getConnections()) { if(connection.getSource() == null || connection.getTarget() == null) { toDelete.add(connection); continue; } final Vertex sourceVertex = connection.getSource(); if(sourceVertex instanceof Instance) { if (connection.getSourcePort() == null || connection.getSourcePort().getName() == null) { toDelete.add(connection); continue; } } final Vertex targetVertex = connection.getTarget(); if(targetVertex instanceof Instance) { if (connection.getTargetPort() == null || connection.getTargetPort().getName() == null) { toDelete.add(connection); continue; } } } if(toDelete.size() == 1){ messagesList.add("1 connection deleted."); } else if(toDelete.size() > 1) { messagesList.add(toDelete.size() + " connections deleted."); } boolean hasDoneChanges = !toDelete.isEmpty(); // Check for issues in network children (instances, ports) for (final Vertex vertex : network.getChildren()) { if (vertex instanceof Instance) { hasDoneChanges |= fixInstance((Instance) vertex, toDelete, messagesList); } // Check ports here, if needed } // Really delete wrong objects for (final EObject eobject : toDelete) { OrccLogger.noticeln("[" + eobject.getClass().getName() + "] " + eobject + " deleted."); EcoreUtil.delete(eobject, true); } return hasDoneChanges; } /** * Check the given instance to detect potential issues, and fix them. For * each fixed instance, a message is appended to the given messagesList and * objects which needs to be deleted are appended to given list * <i>toDelete</i>. * * @param instance * @param messagesList * @param todelete * Objects to delete later. * @return true if something has been modified in the network */ private boolean fixInstance(final Instance instance, final List<EObject> toDelete, final List<String> messagesList) { boolean hasDoneChanges = false; int errorCount = 0; for (final Argument arg : instance.getArguments()) { if (arg.getVariable() == null || arg.getVariable().getName() == null) { toDelete.add(arg); hasDoneChanges = true; errorCount++; } } if (hasDoneChanges) { messagesList.add(errorCount + " argument(s) deleted from instance " + instance.getSimpleName() + "."); } return hasDoneChanges; } /** * Read the given network, create corresponding graphical representation of * Instance/Port/etc. and append them to the given diagram. * * @param network * @param diagram * @return */ public boolean initializeDiagramFromNetwork(final Network network, final Diagram diagram) { final XdfDiagramFeatureProvider xdfFeatureProvider = (XdfDiagramFeatureProvider) getFeatureProvider(); for (Vertex vertex : network.getChildren()) { if (vertex instanceof Instance) { addBoToDiagram(diagram, vertex); } } for (Port port : network.getInputs()) { addBoToDiagram(diagram, port); } for (Port port : network.getOutputs()) { addBoToDiagram(diagram, port); } for (net.sf.orcc.df.Connection connection : network.getConnections()) { final IAddConnectionContext ctxt = XdfUtil.getAddConnectionContext( xdfFeatureProvider, getDiagram(), connection); getFeatureProvider().addIfPossible(ctxt); } // Layout the diagram final IContext context = new CustomContext(); final ICustomFeature layoutFeature = xdfFeatureProvider .getDefaultLayoutFeature(); if (layoutFeature.canExecute(context)) { layoutFeature.execute(context); } // Reset the buffered selection. Without this, the last object of the // diagram will be selected at the first layout() call on any object ((DiagramBehavior) getDiagramBehavior()).setPictogramElementForSelection(null); return true; } /** * Add the given business object to the given diagram if the diagram has at * least a feature supporting the type of bo. * * @param diagram * @param bo * @return */ private PictogramElement addBoToDiagram(final Diagram diagram, final EObject bo) { final AddContext addContext = new AddContext(); addContext.setTargetContainer(diagram); addContext.setNewObject(bo); addContext.setLocation(10, 10); return getFeatureProvider().addIfPossible(addContext); } /** * Check the given network validity. If errors are detected, an error dialog * is shown with details about issues and how to resolve it */ private void checkDiagramValidity (final Diagram diagram) { int missing = 0, badlinks = 0; // Diagram already exists. We check for compatibility between // network and diagram for (final Shape shape : diagram.getChildren()) { final Object bo = getBusinessObjectForPictogramElement(shape); if (bo == null) { missing++; } else if (PropsUtil.isInstance(shape)) { if (!(bo instanceof Instance)) { badlinks++; } } else if (PropsUtil.isInputPort(shape)) { if (!(bo instanceof Port && XdfUtil .isInputNetworkPort((Port) bo))) { badlinks++; } } else if (PropsUtil.isOutputPort(shape)) { if (!(bo instanceof Port && XdfUtil .isOutputNetworkPort((Port) bo))) { badlinks++; } } } if (missing + badlinks != 0) { final StringBuilder errorMsg = new StringBuilder( "There is error in the diagram:"); errorMsg.append(System.getProperty("line.separator")); if (missing != 0) { errorMsg.append(missing); errorMsg.append(" shape(s) have no business object linked"); errorMsg.append(System.getProperty("line.separator")); } if (badlinks != 0) { errorMsg.append(badlinks); errorMsg.append(" shape(s) are linked to a business object with the wrong type"); errorMsg.append(System.getProperty("line.separator")); } errorMsg.append("Please re-generate the diagram (delete it and re-open the network)."); MessageDialog.openError(XdfUtil.getDefaultShell(), "Issues in diagram", errorMsg.toString()); } } /** * Check if this diagram is outdated and update it according to the version * number stored in its properties. * * @param diagram */ private void updateVersion(final Diagram diagram) { final Property property = Graphiti.getPeService().getProperty(diagram, GLOBAL_VERSION_KEY); if (property == null || property.getValue() == null) { // The Diagram has just been created: set version to "current" Graphiti.getPeService().setPropertyValue(diagram, GLOBAL_VERSION_KEY, String.valueOf(CURRENT_EDITOR_VERSION)); return; } final int version = Integer.parseInt(property.getValue()); if (CURRENT_EDITOR_VERSION == version) { // The diagram is up-to-date, nothing to do return; } /* * Eclipse Luna comes with Graphiti 0.11. This new Graphiti version * replaces 'angle' feature with 'rotation' equivalent. Unfortunately, * 'rotation' is not defined in Graphiti model before 0.11. If a xdfdiag * contains an 'angle' value, it will be converted by newer versions to * 'rotation'. As a result, the diagram will become invalid for all * older versions of Orcc. To fix the issue, we force to remove all * usage of 'angle' property. They are not used and were in the default * "COMMON_TEXT" style only because of a copy/paste from Graphiti doc. */ if (version <= VERSION_1) { for (Style style : diagram.getStyles()) { if (style.eIsSet(StylesPackage.eINSTANCE.getStyle_Angle())) { style.eUnset(StylesPackage.eINSTANCE.getStyle_Angle()); } } } property.setValue(String.valueOf(CURRENT_EDITOR_VERSION)); } }