/* $Id: PGMLStackParser.java 18024 2010-02-16 21:25:10Z bobtarling $ ***************************************************************************** * Copyright (c) 2009 Contributors - see below * 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: * Michiel Van Der Wulp * Bob Tarling * Thomas Neustupny ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // Copyright (c) 2005-2009 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph appear in all copies. This software program and // documentation are copyrighted by The Regents of the University of // California. The software program and documentation are supplied "AS // IS", without any accompanying services from The Regents. The Regents // does not warrant that the operation of the program will be // uninterrupted or error-free. The end-user understands that the program // was developed for research purposes and is advised not to rely // exclusively on the program for any reason. IN NO EVENT SHALL THE // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. package org.argouml.persistence; import java.awt.Rectangle; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.apache.log4j.Logger; import org.argouml.model.Model; import org.argouml.uml.diagram.ArgoDiagram; import org.argouml.uml.diagram.DiagramEdgeSettings; import org.argouml.uml.diagram.DiagramSettings; import org.argouml.uml.diagram.PathContainer; import org.argouml.uml.diagram.StereotypeContainer; import org.argouml.uml.diagram.VisibilityContainer; import org.argouml.uml.diagram.ui.FigCompartmentBox; import org.argouml.uml.diagram.ui.FigEdgeModelElement; import org.argouml.uml.diagram.ui.FigEdgePort; import org.tigris.gef.base.Diagram; import org.tigris.gef.persistence.pgml.Container; import org.tigris.gef.persistence.pgml.FigEdgeHandler; import org.tigris.gef.persistence.pgml.FigGroupHandler; import org.tigris.gef.persistence.pgml.HandlerStack; import org.tigris.gef.presentation.Fig; import org.tigris.gef.presentation.FigEdge; import org.tigris.gef.presentation.FigGroup; import org.tigris.gef.presentation.FigNode; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; // TODO: Move to Diagram subsystem? /** * The PGML Parser. * <p> * * This replaces much of the identically named class from GEF. */ class PGMLStackParser extends org.tigris.gef.persistence.pgml.PGMLStackParser { private static final Logger LOG = Logger.getLogger(PGMLStackParser.class); private List<EdgeData> figEdges = new ArrayList<EdgeData>(50); private LinkedHashMap<FigEdge, Object> modelElementsByFigEdge = new LinkedHashMap<FigEdge, Object>( 50); private DiagramSettings diagramSettings; // TODO: Use stylesheet to convert or wait till we use Fig // factories in diagram subsystem. // What is the last version that used FigNote? private void addTranslations() { addTranslation("org.argouml.uml.diagram.ui.FigNote", "org.argouml.uml.diagram.static_structure.ui.FigComment"); addTranslation("org.argouml.uml.diagram.static_structure.ui.FigNote", "org.argouml.uml.diagram.static_structure.ui.FigComment"); addTranslation("org.argouml.uml.diagram.state.ui.FigState", "org.argouml.uml.diagram.state.ui.FigSimpleState"); addTranslation("org.argouml.uml.diagram.ui.FigCommentPort", "org.argouml.uml.diagram.ui.FigEdgePort"); addTranslation("org.tigris.gef.presentation.FigText", "org.argouml.uml.diagram.ui.ArgoFigText"); addTranslation("org.tigris.gef.presentation.FigLine", "org.argouml.gefext.ArgoFigLine"); addTranslation("org.tigris.gef.presentation.FigPoly", "org.argouml.gefext.ArgoFigPoly"); addTranslation("org.tigris.gef.presentation.FigCircle", "org.argouml.gefext.ArgoFigCircle"); addTranslation("org.tigris.gef.presentation.FigRect", "org.argouml.gefext.ArgoFigRect"); addTranslation("org.tigris.gef.presentation.FigRRect", "org.argouml.gefext.ArgoFigRRect"); addTranslation( "org.argouml.uml.diagram.deployment.ui.FigMNodeInstance", "org.argouml.uml.diagram.deployment.ui.FigNodeInstance"); addTranslation("org.argouml.uml.diagram.ui.FigRealization", "org.argouml.uml.diagram.ui.FigAbstraction"); } /** * Construct a PGML parser with the given HREF/Object map and default * diagram settings. * * @param modelElementsByUuid map of HREF ids to objects used to associate * Figs with their owning model elements * @param defaultSettings default diagram settings to use for newly created * diagram and its contained Figs */ public PGMLStackParser(Map<String, Object> modelElementsByUuid, DiagramSettings defaultSettings) { super(modelElementsByUuid); addTranslations(); // Create a new diagram wide settings block which is backed by // the project-wide defaults that we were passed diagramSettings = new DiagramSettings(defaultSettings); } /* * @see org.tigris.gef.persistence.pgml.HandlerFactory#getHandler( * HandlerStack, Object, String, String, String, Attributes) */ @Override public DefaultHandler getHandler(HandlerStack stack, Object container, String uri, String localname, String qname, Attributes attributes) throws SAXException { String href = attributes.getValue("href"); Object owner = null; if (href != null) { owner = findOwner(href); if (owner == null) { LOG.warn("Found href of " + href + " with no matching element in model"); return null; } } // Ignore non-private elements within FigNode groups if (container instanceof FigGroupHandler) { FigGroup group = ((FigGroupHandler) container).getFigGroup(); if (group instanceof FigNode && !qname.equals("private")) { return null; } } // Handle ItemUID in container contents if (qname.equals("private") && (container instanceof Container)) { return new PrivateHandler(this, (Container) container); } DefaultHandler handler = super.getHandler(stack, container, uri, localname, qname, attributes); if (handler instanceof FigEdgeHandler) { return new org.argouml.persistence.FigEdgeHandler(this, ((FigEdgeHandler) handler).getFigEdge()); } return handler; } /* * @see org.tigris.gef.persistence.pgml.PGMLStackParser#setAttrs( * org.tigris.gef.presentation.Fig, org.xml.sax.Attributes) */ @Override protected final void setAttrs(Fig f, Attributes attrList) throws SAXException { if (f instanceof FigGroup) { FigGroup group = (FigGroup) f; String clsNameBounds = attrList.getValue("description"); if (clsNameBounds != null) { StringTokenizer st = new StringTokenizer(clsNameBounds, ",;[] "); // Discard class name, x y w h if (st.hasMoreElements()) { st.nextToken(); } if (st.hasMoreElements()) { st.nextToken(); } if (st.hasMoreElements()) { st.nextToken(); } if (st.hasMoreElements()) { st.nextToken(); } if (st.hasMoreElements()) { st.nextToken(); } Map<String, String> attributeMap = interpretStyle(st); setStyleAttributes(group, attributeMap); } } // TODO: Attempt to move the following code to GEF String name = attrList.getValue("name"); if (name != null && !name.equals("")) { registerFig(f, name); } setCommonAttrs(f, attrList); final String href = attrList.getValue("href"); if (href != null && !href.equals("")) { Object modelElement = findOwner(href); if (modelElement == null) { LOG.error("Can't find href of " + href); throw new SAXException("Found href of " + href + " with no matching element in model"); } // The owner should always have already been set in the constructor if (f.getOwner() != modelElement) { // Assign nodes immediately but edges later. See issue 4310. if (f instanceof FigEdge) { modelElementsByFigEdge.put((FigEdge) f, modelElement); } else { f.setOwner(modelElement); } } else { LOG.debug("Ignoring href on " + f.getClass().getName() + " as it's already set"); } } } /** * The StringTokenizer is expected to be positioned at the start of a string * of style identifiers in the format name=value;name=value;name=value.... * Each name value pair is interpreted and the Fig configured accordingly. * The value is optional and will default to a value applicable for its * name. The current applicable names are operationsVisible and * attributesVisible and are used to show or hide the compartments within * Class and Interface. The default values are true. * * @param st The StrinkTokenizer positioned at the first style identifier * @return a map of attributes */ private Map<String, String> interpretStyle(StringTokenizer st) { Map<String, String> map = new HashMap<String, String>(); String name; String value; while (st.hasMoreElements()) { String namevaluepair = st.nextToken(); int equalsPos = namevaluepair.indexOf('='); if (equalsPos < 0) { name = namevaluepair; value = "true"; } else { name = namevaluepair.substring(0, equalsPos); value = namevaluepair.substring(equalsPos + 1); } map.put(name, value); } return map; } /** * Set the fig style attributes. * <p> * * TODO: This should move into the render factories as described in issue * 859. * * @param fig the fig to style. * @param attributeMap a map of name value pairs */ private void setStyleAttributes(Fig fig, Map<String, String> attributeMap) { for (Map.Entry<String, String> entry : attributeMap.entrySet()) { final String name = entry.getKey(); final String value = entry.getValue(); if (fig instanceof FigCompartmentBox) { FigCompartmentBox fcb = (FigCompartmentBox) fig; if ("operationsVisible".equals(name)) { fcb.showCompartment(Model.getMetaTypes().getOperation(), value.equalsIgnoreCase("true")); } else if ("attributesVisible".equals(name)) { fcb.showCompartment(Model.getMetaTypes().getAttribute(), value.equalsIgnoreCase("true")); } else if ("enumerationLiteralsVisible".equals(name)) { fcb.showCompartment(Model.getMetaTypes() .getEnumerationLiteral(), value .equalsIgnoreCase("true")); } else if ("extensionPointVisible".equals(name)) { fcb.showCompartment(Model.getMetaTypes() .getExtensionPoint(), value .equalsIgnoreCase("true")); } } if ("stereotypeVisible".equals(name)) { ((StereotypeContainer) fig).setStereotypeVisible(value .equalsIgnoreCase("true")); } else if ("visibilityVisible".equals(name)) { ((VisibilityContainer) fig).setVisibilityVisible(value .equalsIgnoreCase("true")); } else if ("pathVisible".equals(name)) { ((PathContainer) fig).setPathVisible(value .equalsIgnoreCase("true")); } } } /** * Read and parse the input stream to create a new diagram and return it. * * @param is the input stream * @param closeStream true to close the stream when parsing is complete * @return the diagram created as a result of the parse * @throws SAXException */ public ArgoDiagram readArgoDiagram(InputSource is, boolean closeStream) throws SAXException { InputStream stream = is.getByteStream(); if (stream == null) { try { // happens when 'is' comes from a zip file URL url = new URL(is.getSystemId()); stream = url.openStream(); closeStream = true; } catch (Exception e) { // continue with null stream, readDiagram(...) will take care of // it } } return (ArgoDiagram) readDiagram(stream, closeStream); } /** * Read and parse the input stream to create a new diagram and return it. * * @param is the input stream * @param closeStream true to close the stream when parsing is complete * @return the diagram created as a result of the parse * @throws SAXException */ public ArgoDiagram readArgoDiagram(InputStream is, boolean closeStream) throws SAXException { return (ArgoDiagram) readDiagram(is, closeStream); } @Override public Diagram readDiagram(InputStream is, boolean closeStream) throws SAXException { // TODO: we really want to be able replace the initial content handler // which is passed to SAX, but we can't do this without cloning a // whole bunch of code because it's private in the super class. Diagram d = super.readDiagram(is, closeStream); attachEdges(d); return d; } /** * This is called when all nodes and edges have been read and placed on the * diagram. This method then attaches the edges to the correct node, * including the nodes contained within edges allowing edge to edge * connections for comment edges, association classes and dependencies. * * @param d the Diagram */ private void attachEdges(Diagram d) { for (EdgeData edgeData : figEdges) { final FigEdge edge = edgeData.getFigEdge(); Object modelElement = modelElementsByFigEdge.get(edge); if (modelElement != null) { if (edge.getOwner() == null) { edge.setOwner(modelElement); } } } for (EdgeData edgeData : figEdges) { final FigEdge edge = edgeData.getFigEdge(); Fig sourcePortFig = findFig(edgeData.getSourcePortFigId()); Fig destPortFig = findFig(edgeData.getDestPortFigId()); final FigNode sourceFigNode = getFigNode(edgeData .getSourceFigNodeId()); final FigNode destFigNode = getFigNode(edgeData.getDestFigNodeId()); if (sourceFigNode instanceof FigEdgePort) { sourcePortFig = sourceFigNode; } if (destFigNode instanceof FigEdgePort) { destPortFig = destFigNode; } if (sourcePortFig == null && sourceFigNode != null) { sourcePortFig = getPortFig(sourceFigNode); } if (destPortFig == null && destFigNode != null) { destPortFig = getPortFig(destFigNode); } if (sourcePortFig == null || destPortFig == null || sourceFigNode == null || destFigNode == null) { LOG.error("Can't find nodes for FigEdge: " + edge.getId() + ":" + edge.toString()); edge.removeFromDiagram(); } else { edge.setSourcePortFig(sourcePortFig); edge.setDestPortFig(destPortFig); edge.setSourceFigNode(sourceFigNode); edge.setDestFigNode(destFigNode); } } // Once all edges are connected do a compute route on each to make // sure that annotations and the edge port is positioned correctly // Only do this after all edges are connected as compute route // requires all edges to be connected to nodes. // TODO: It would be nice not to have to do this and restore annotation // positions instead. for (Object edge : d.getLayer().getContentsEdgesOnly()) { FigEdge figEdge = (FigEdge) edge; figEdge.computeRouteImpl(); } } // TODO: Move to GEF /** * Store data of a FigEdge together with the id's of nodes to connect to * * @param figEdge The FigEdge * @param sourcePortFigId The id of the source port * @param destPortFigId The id of the destination port * @param sourceFigNodeId The id of the source node * @param destFigNodeId The id of the destination node */ public void addFigEdge(final FigEdge figEdge, final String sourcePortFigId, final String destPortFigId, final String sourceFigNodeId, final String destFigNodeId) { figEdges.add(new EdgeData(figEdge, sourcePortFigId, destPortFigId, sourceFigNodeId, destFigNodeId)); } // TODO: Move to GEF /** * Get the FigNode that the fig id represents. * * @param figId (In the form Figx.y.z) * @return the FigNode with the given id * @throws IllegalStateException if the figId supplied is not of a FigNode */ private FigNode getFigNode(String figId) throws IllegalStateException { if (figId.contains(".")) { // If the id does not look like a top-level Fig then we can assume // that this is an id of a FigEdgePort inside some FigEdge. // So extract the FigEdgePort from the FigEdge and return that as // the FigNode. figId = figId.substring(0, figId.indexOf('.')); FigEdgeModelElement edge = (FigEdgeModelElement) findFig(figId); if (edge == null) { throw new IllegalStateException("Can't find a FigNode with id " + figId); } edge.makeEdgePort(); return edge.getEdgePort(); } else { // If there is no dot then this must be a top level Fig and can be // assumed to be a FigNode. Fig f = findFig(figId); if (f instanceof FigNode) { return (FigNode) f; } else { LOG.error("FigID " + figId + " is not a node, edge ignored"); return null; } } } // TODO: Move to GEF /** * Get the Fig from the FigNode that is the port. * * @param figNode the FigNode * @return the Fig that is the port on the given FigNode */ private Fig getPortFig(FigNode figNode) { if (figNode instanceof FigEdgePort) { // TODO: Can we just do this every time, no need for else - Bob return figNode; } else { return (Fig) figNode.getPortFigs().get(0); } } // TODO: Move to GEF /** * The data from an edge extracted from the PGML before we can guarantee all * the nodes have been constructed. This stores the FigEdge and the id's of * the nodes to connect to later. If the nodes are not known then the ports * are returned instead. */ private class EdgeData { private final FigEdge figEdge; private final String sourcePortFigId; private final String destPortFigId; private final String sourceFigNodeId; private final String destFigNodeId; /** * Constructor * * @param edge The FigEdge * @param sourcePortId The id of the source port * @param destPortId The id of the destination port * @param sourceNodeId The id of the source node * @param destNodeId The id of the destination node */ public EdgeData(FigEdge edge, String sourcePortId, String destPortId, String sourceNodeId, String destNodeId) { if (sourcePortId == null || destPortId == null) { throw new IllegalArgumentException( "source port and dest port must not be null" + " source = " + sourcePortId + " dest = " + destPortId + " figEdge = " + edge); } this.figEdge = edge; this.sourcePortFigId = sourcePortId; this.destPortFigId = destPortId; this.sourceFigNodeId = sourceNodeId != null ? sourceNodeId : sourcePortId; this.destFigNodeId = destNodeId != null ? destNodeId : destPortId; } /** * Get the id of the destination FigNode * * @return the id */ public String getDestFigNodeId() { return destFigNodeId; } /** * Get the id of the destination port * * @return the id */ public String getDestPortFigId() { return destPortFigId; } /** * Get the FigEdge * * @return the FigEdge */ public FigEdge getFigEdge() { return figEdge; } /** * Get the id of the source FigNode * * @return the id */ public String getSourceFigNodeId() { return sourceFigNodeId; } /** * Get the id of the source port * * @return the id */ public String getSourcePortFigId() { return sourcePortFigId; } } /** * Construct a new instance of the named Fig with the owner represented by * the given href and the bounds parsed from the PGML file. We look for * constructors of the form Fig(Object owner, Rectangle bounds, * DiagramSettings settings) which is typically used for subclasses of * FigNodeModelElement, then Fig(Object owner, DiagramSettings settings) * which is used for subclasses of FigEdgeModelElement. * <p> * If we fail to find any of the constructors that we know about, we'll call * GEF's version of this method to see if it can find a constructor. * * @param className fully qualified name of class to instantiate * @param href string representing UUID of owning element * @param bounds position and size of figure * @return * @throws SAXException * @see org.tigris.gef.persistence.pgml.PGMLStackParser#constructFig(java.lang.String, * java.lang.String, java.awt.Rectangle) */ @Override protected Fig constructFig(final String className, final String href, final Rectangle bounds, final Attributes attributes) throws SAXException { final DiagramSettings diagramSettings = ((ArgoDiagram) getDiagram()) .getDiagramSettings(); Fig f = null; try { Class figClass = Class.forName(className); final Constructor[] constructors = figClass.getConstructors(); // We are looking first to match with 3 different constructor // types. We would not expect a Fig to have any mix of these. // Any constructor other than these should be deprecated so we // look for these first. // Fig(DiagramEdgeSettings, DiagramSettings) // Fig(Object, Rectangle, DiagramSettings) // Fig(Rectangle, DiagramSettings) for (Constructor constructor : constructors) { Class[] parameterTypes = constructor.getParameterTypes(); if (parameterTypes.length == 3 && parameterTypes[0].equals(Object.class) && parameterTypes[1].equals(Rectangle.class) && parameterTypes[2].equals(DiagramSettings.class)) { // FigNodeModelElements should match here final Object parameters[] = new Object[3]; final Object owner = getOwner(className, href); if (owner == null) { return null; } parameters[0] = owner; parameters[1] = bounds; parameters[2] = diagramSettings; constructor.setAccessible(true); f = (Fig) constructor.newInstance(parameters); } else if (parameterTypes.length == 2 && parameterTypes[0].equals(DiagramEdgeSettings.class) && parameterTypes[1].equals(DiagramSettings.class)) { // FigEdgeModelElements should match here (they have no // bounds) final Object parameters[] = new Object[2]; final Object owner = getOwner(className, href); if (owner == null) { return null; } String sourceUuid = attributes.getValue("sourceConnector"); String destinationUuid = attributes .getValue("destConnector"); final Object source; final Object destination; if (sourceUuid != null && destinationUuid != null) { source = findOwner(sourceUuid); destination = findOwner(destinationUuid); } else { source = null; destination = null; } DiagramEdgeSettings settings = new DiagramEdgeSettings( owner, source, destination); parameters[0] = settings; parameters[1] = diagramSettings; constructor.setAccessible(true); f = (Fig) constructor.newInstance(parameters); } else if (parameterTypes.length == 2 && parameterTypes[0].equals(Rectangle.class) && parameterTypes[1].equals(DiagramSettings.class)) { // A FigNodeModelElement with no owner should match here // TODO: This is a temporary solution due to FigPool // extending // FigNodeModelElement when in fact it should not do so. Object parameters[] = new Object[2]; parameters[0] = bounds; parameters[1] = diagramSettings; constructor.setAccessible(true); f = (Fig) constructor.newInstance(parameters); } } if (f == null) { // If no Fig was created by the code above then we must go // look for the old style constructor that should have fallen // into disuse by now. // Fig(Object, Rectangle, DiagramSettings) // All of these constructors should have been deprecated // at least and replaced with the new signature. This is // here for paranoia only until all Figs have been reviewed. for (Constructor constructor : constructors) { Class[] parameterTypes = constructor.getParameterTypes(); if (parameterTypes.length == 2 && parameterTypes[0].equals(Object.class) && parameterTypes[1].equals(DiagramSettings.class)) { Object parameters[] = new Object[2]; final Object owner = getOwner(className, href); // currently FigEdgeNote can be passed null // if (owner == null) { // return null; // } parameters[0] = owner; parameters[1] = diagramSettings; constructor.setAccessible(true); f = (Fig) constructor.newInstance(parameters); LOG.warn("Fig created by old style constructor " + f.getClass().getName()); break; } } } } catch (ClassNotFoundException e) { throw new SAXException(e); } catch (IllegalAccessException e) { throw new SAXException(e); } catch (InstantiationException e) { throw new SAXException(e); } catch (InvocationTargetException e) { throw new SAXException(e); } // Fall back to GEF's handling if we couldn't find an appropriate // constructor if (f == null) { LOG.warn("No ArgoUML constructor found for " + className + " falling back to GEF's default constructors"); f = super.constructFig(className, href, bounds, attributes); } return f; } /** * Given the href extracted from the PGML return the model element with that * uuid. * * @param className Used only for logging should the href not be found * @param href The href * @return */ private Object getOwner(String className, String id) { if (id == null) { LOG.warn("There is no href attribute provided for a " + className + " so the diagram element is ignored on load"); return null; } final Object owner = findOwner(id); if (owner == null) { LOG.warn("The href " + id + " is not found for a " + className + " so the diagram element is ignored on load"); return null; } return owner; } /** * Save the newly created Diagram for use by the parser. We take the * opportunity to attach our default diagram settings to it so we'll have * them if needed when constructing Figs. * <p> * Diagrams are created in GEF's PGMLHandler.initDiagram() which is private * and can't be overridden. Initialization sequence is: * <ul> * <li>load diagram class using name in PGML file * <li>instantiate using 0-arg constructor * <li>invoke this method (setDiagram(<newDiagramInstance)) * <li>invoke diagram's initialize(Object owner) method * <li>diagram.setName() * <li>diagram.setScale() * <li>diagram.setShowSingleMultiplicity() (?!why does GEF care about * multiplicity?!) * </ul> * * @param diagram the new diagram * @see org.tigris.gef.persistence.pgml.PGMLStackParser#setDiagram(org.tigris.gef.base.Diagram) */ @Override public void setDiagram(Diagram diagram) { // TODO: We could generalize this to initialize more stuff if needed ((ArgoDiagram) diagram).setDiagramSettings(getDiagramSettings()); super.setDiagram(diagram); } public DiagramSettings getDiagramSettings() { return diagramSettings; } }