/******************************************************************************* * Copyright (c) 2004, 2010 Spring IDE Developers * 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: * Spring IDE Developers - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.beans.ui.graph.model; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; 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.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.Platform; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.graph.DirectedGraph; import org.eclipse.draw2d.graph.DirectedGraphLayout; import org.eclipse.draw2d.graph.Edge; import org.eclipse.draw2d.graph.EdgeList; import org.eclipse.draw2d.graph.Node; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.graphics.Font; import org.springframework.ide.eclipse.beans.core.BeansCorePlugin; import org.springframework.ide.eclipse.beans.core.internal.model.BeansConnection; import org.springframework.ide.eclipse.beans.core.internal.model.BeansConnection.BeanType; import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils; import org.springframework.ide.eclipse.beans.core.model.IBean; import org.springframework.ide.eclipse.beans.core.model.IBeansComponent; import org.springframework.ide.eclipse.beans.core.model.IBeansConfig; import org.springframework.ide.eclipse.beans.core.model.IBeansConfigSet; import org.springframework.ide.eclipse.beans.core.model.IBeansModelElement; import org.springframework.ide.eclipse.beans.ui.BeansUIPlugin; import org.springframework.ide.eclipse.beans.ui.graph.BeansGraphPlugin; import org.springframework.ide.eclipse.beans.ui.graph.editor.GraphEditorInput; import org.springframework.ide.eclipse.beans.ui.graph.figures.BeanFigure; import org.springframework.ide.eclipse.core.SpringCoreUtils; import org.springframework.ide.eclipse.core.model.IModelElement; /** * This class builds the graphical representation of the model data (given as {@link GraphEditorInput}) via GEF's * {@link DirectedGraphLayout}. * @author Torsten Juergeleit * @author Christian Dupuis */ public class Graph implements IAdaptable { private static final String CLASS_ATTRIBUTE = "class"; private static final String GRAPH_CONTENT_EXTENDER_EXTENSION_POINT = BeansGraphPlugin.PLUGIN_ID + ".graphContentExtender"; /* * Max width of rows with orphan beans (unconnected beans) if no subgraph is available */ private static final int MAX_ORPHAN_ROW_WIDTH = 600; /* Default amount of empty space to be left around a node */ private static final Insets DEFAULT_PADDING = new Insets(16); private static final String ERROR_TITLE = "Graph.error.title"; private GraphEditorInput input; private DirectedGraph graph; private Map<String, Bean> beans = new HashMap<String, Bean>(); private List<Reference> beanReferences = new ArrayList<Reference>(); private String elementId; private String contextId; public Graph() { graph = new DirectedGraph(); } public Graph(GraphEditorInput input) { this.input = input; this.elementId = input.getElementId(); this.contextId = input.getContextId(); } /** * Initializes the embedded graph with nodes from GraphEditorInput's beans and edges from GraphEditorInput's bean * references. */ @SuppressWarnings("unchecked") public void init() { createBeansMap(); createReferences(); extendGraphContent(); graph = new DirectedGraph(); for (Bean bean : beans.values()) { graph.nodes.add(bean); } for (Reference reference : beanReferences) { graph.edges.add(reference); } } public Object getAdapter(Class adapter) { return input.getAdapter(adapter); } protected Collection getBeans() { return beans.values(); } protected Bean getBean(String name) { return (Bean) beans.get(name); } public List getNodes() { return graph.nodes; } @SuppressWarnings({ "unchecked", "deprecation" }) public void layout(Font font) { // Iterate through all graph nodes (beans) to calculate label width Iterator beans = graph.nodes.iterator(); while (beans.hasNext()) { Bean bean = (Bean) beans.next(); // Calculate bean's dimension with a temporary bean figure BeanFigure dummy = new BeanFigure(bean); dummy.setFont(font); Dimension size = dummy.getPreferredSize(); bean.width = size.width; bean.height = size.height; bean.preferredHeight = size.height; } // Remove all unreferenced single beans and connect all unreferenced // subgraphs with a temporary root bean Bean root = new Bean(); graph.nodes.add(root); EdgeList rootEdges = new EdgeList(); List<Bean> orphanBeans = new ArrayList<Bean>(); beans = getBeans().iterator(); while (beans.hasNext()) { Bean bean = (Bean) beans.next(); if (bean.incoming.isEmpty() && bean.outgoing.isEmpty()) { orphanBeans.add(bean); graph.nodes.remove(bean); } else { Reference reference = new Reference(BeanType.STANDARD, root, bean, false); reference.weight = 0; rootEdges.add(reference); graph.edges.add(reference); } } // Calculate position of all beans in graph try { new DirectedGraphLayout().visit(graph); // Re-invert edges inverted while breaking cycles; this only seems to be required on earlier GEF versions if (!SpringCoreUtils.isEclipseSameOrNewer(3, 6)) { for (int i = 0; i < graph.edges.size(); i++) { Edge e = graph.edges.getEdge(i); if (e.isFeedback()) { e.invert(); } } } // Remove temporary root and root edges for (int i = 0; i < rootEdges.size(); i++) { Edge e = rootEdges.getEdge(i); e.source.outgoing.remove(e); e.target.incoming.remove(e); graph.edges.remove(e); } graph.nodes.remove(root); // Re-align nodes and edges' bend points topmost vertical position int maxY = 0; // max height of graph int maxX = 0; // max width of graph int ranks = graph.ranks.size(); if (ranks > 1) { int deltaY = graph.ranks.getRank(1).getNode(0).y; Iterator nodes = graph.nodes.iterator(); while (nodes.hasNext()) { Bean node = (Bean) nodes.next(); // Move node vertically and update max height node.y -= deltaY; if ((node.y + node.height) > maxY) { maxY = node.y + node.height; } // Update max width if ((node.x + node.width) > maxX) { maxX = node.x + node.width; } } Iterator edges = graph.edges.iterator(); while (edges.hasNext()) { Edge edge = (Edge) edges.next(); if (edge.vNodes != null) { Iterator points = edge.vNodes.iterator(); while (points.hasNext()) { Node node = (Node) points.next(); node.y -= deltaY; } } } } // Re-add all unconnected beans to the bottom of the graph int x = 0; // current horizontal position in current row int y = maxY; // current row if (maxY > 0) { y += DEFAULT_PADDING.getHeight(); } if (maxX < MAX_ORPHAN_ROW_WIDTH) { maxX = MAX_ORPHAN_ROW_WIDTH; } maxY = 0; // max height of all figures in current row beans = orphanBeans.iterator(); while (beans.hasNext()) { Bean bean = (Bean) beans.next(); // If current row is filled then start new row if ((x + bean.width) > maxX) { bean.x = x = 0; bean.y = y += maxY + DEFAULT_PADDING.getHeight(); maxY = bean.height; } else { bean.y = y; bean.x = x; if (bean.height > maxY) { maxY = bean.height; } } x += bean.width + DEFAULT_PADDING.getWidth(); graph.nodes.add(bean); } } catch (RuntimeException e) { // If an error occured during layouting (graph contains cylces, // graph not fully connected, ...) then clear graph, invalidate // editor input (not saved when Eclipse is closed) and display an // error message graph = new DirectedGraph(); input.setHasError(true); MessageDialog.openError(BeansGraphPlugin.getActiveWorkbenchWindow().getShell(), BeansGraphPlugin.getResourceString(ERROR_TITLE), e.getMessage()); } } @SuppressWarnings("deprecation") protected void extendGraphContent() { if (BeansUIPlugin.getDefault().getPluginPreferences() .getBoolean(BeansUIPlugin.SHOULD_SHOW_EXTENDED_CONTENT_PREFERENCE_ID)) { IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint( GRAPH_CONTENT_EXTENDER_EXTENSION_POINT); if (point != null) { for (IExtension extension : point.getExtensions()) { for (IConfigurationElement config : extension.getConfigurationElements()) { if (config.getAttribute(CLASS_ATTRIBUTE) != null) { try { Object provider = config.createExecutableExtension(CLASS_ATTRIBUTE); if (provider instanceof IGraphContentExtender) { ((IGraphContentExtender) provider).addAdditionalBeans(beans, beanReferences, (IBeansModelElement) getElement(elementId), (IBeansModelElement) getElement(contextId)); } } catch (CoreException e) { BeansGraphPlugin.log(e); } } } } } } } /** * Creates a list with all beans belonging to the specified config / config set or being connected with the * specified bean. */ protected void createBeansMap() { Set<IBean> list = new LinkedHashSet<IBean>(); if (getElement(elementId) instanceof IBeansConfig) { IBeansConfig bc = (IBeansConfig) getElement(elementId); list.addAll(bc.getBeans()); // add component registered beans addBeansFromComponents(list, bc.getComponents()); } else if (getElement(elementId) instanceof IBeansConfigSet) { IBeansConfigSet bcs = (IBeansConfigSet) getElement(elementId); list.addAll(bcs.getBeans()); // add component registered beans addBeansFromComponents(list, bcs.getComponents()); } else if (getElement(elementId) instanceof IBean) { list.add((IBean) getElement(elementId)); for (BeansConnection beanRef : BeansModelUtils.getBeanReferences(getElement(elementId), getElement(contextId), true)) { if (beanRef.getType() != BeanType.INNER) { list.add(beanRef.getTarget()); } } } // Marshall all beans into a graph bean node beans = new LinkedHashMap<String, Bean>(); for (IBean bean : list) { if (shouldAddBean(bean)) { beans.put(bean.getElementName(), new Bean(bean)); } } } @SuppressWarnings("deprecation") private boolean shouldAddBean(IBean bean) { return !bean.isInfrastructure() || (bean.isInfrastructure() && BeansUIPlugin.getDefault().getPluginPreferences() .getBoolean(BeansUIPlugin.SHOULD_SHOW_INFRASTRUCTURE_BEANS_PREFERENCE_ID)); } private void addBeansFromComponents(Set<IBean> beans, Set<IBeansComponent> components) { for (IBeansComponent component : components) { Set<IBean> nestedBeans = component.getBeans(); for (IBean nestedBean : nestedBeans) { if (shouldAddBean(nestedBean)) { beans.add(nestedBean); } } addBeansFromComponents(beans, component.getComponents()); } } protected void createReferences() { beanReferences = new ArrayList<Reference>(); // Add all beans defined in GraphEditorInput as nodes to the graph Iterator beans = this.beans.values().iterator(); while (beans.hasNext()) { Bean bean = (Bean) beans.next(); // Add all beans references from bean (parent, factory or // depends-on beans) to list of graph edges Iterator beanRefs = BeansModelUtils.getBeanReferences(bean.getBean(), BeansCorePlugin.getModel().getElement(contextId), false).iterator(); while (beanRefs.hasNext()) { BeansConnection beanRef = (BeansConnection) beanRefs.next(); Bean targetBean = this.beans.get(beanRef.getTarget().getElementName()); if (targetBean != null && targetBean != bean && beanRef.getSource() instanceof IBean) { beanReferences.add(new Reference(beanRef.getType(), bean, targetBean, bean, beanRef.isInner())); } } // Add all bean references in bean's constructor arguments to list // of graph edges ConstructorArgument[] cargs = bean.getConstructorArguments(); for (ConstructorArgument carg : cargs) { Iterator cargRefs = BeansModelUtils.getBeanReferences(carg.getBeanConstructorArgument(), BeansCorePlugin.getModel().getElement(contextId), false).iterator(); while (cargRefs.hasNext()) { BeansConnection beanRef = (BeansConnection) cargRefs.next(); Bean targetBean = this.beans.get(beanRef.getTarget().getElementName()); if (targetBean != null && targetBean != bean) { beanReferences.add(new Reference(beanRef.getType(), bean, targetBean, carg, beanRef.isInner())); } } } // Add all bean references in properties to list of graph edges Property[] properties = bean.getProperties(); for (Property property : properties) { Iterator propRefs = BeansModelUtils.getBeanReferences(property.getBeanProperty(), BeansCorePlugin.getModel().getElement(contextId), false).iterator(); while (propRefs.hasNext()) { BeansConnection beanRef = (BeansConnection) propRefs.next(); Bean targetBean = this.beans.get(beanRef.getTarget().getElementName()); if (targetBean != null && targetBean != bean) { beanReferences.add(new Reference(beanRef.getType(), bean, targetBean, property, beanRef .isInner())); } } } } } private IModelElement getElement(String elementId) { return BeansCorePlugin.getModel().getElement(elementId); } }