Package org.apache.cayenne.modeler.graph

Source Code of org.apache.cayenne.modeler.graph.BaseGraphBuilder

/*****************************************************************
*   Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you under the Apache License, Version 2.0 (the
*  "License"); you may not use this file except in compliance
*  with the License.  You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
****************************************************************/
package org.apache.cayenne.modeler.graph;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.JPopupMenu;
import javax.swing.border.LineBorder;
import javax.swing.event.UndoableEditEvent;

import org.apache.cayenne.configuration.DataChannelDescriptor;
import org.apache.cayenne.configuration.event.DataMapEvent;
import org.apache.cayenne.configuration.event.DataMapListener;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.Entity;
import org.apache.cayenne.map.Relationship;
import org.apache.cayenne.map.event.EntityEvent;
import org.apache.cayenne.map.event.RelationshipEvent;
import org.apache.cayenne.modeler.Application;
import org.apache.cayenne.modeler.ProjectController;
import org.apache.cayenne.modeler.action.CreateAttributeAction;
import org.apache.cayenne.modeler.action.CreateRelationshipAction;
import org.apache.cayenne.modeler.action.ActionManager;
import org.apache.cayenne.modeler.graph.action.EntityDisplayAction;
import org.apache.cayenne.modeler.graph.action.RemoveEntityAction;
import org.apache.cayenne.util.XMLEncoder;
import org.jgraph.JGraph;
import org.jgraph.graph.DefaultCellViewFactory;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultGraphModel;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphLayoutCache;
import org.jgraph.graph.GraphModel;

import com.jgraph.layout.JGraphFacade;
import com.jgraph.layout.organic.JGraphOrganicLayout;

/**
* Base class for building graphs of entities
*/
abstract class BaseGraphBuilder implements GraphBuilder, DataMapListener {

    static final Font EDGE_FONT = new Font("Verdana", 0, 10);

    /**
     * Graph
     */
    protected JGraph graph;

    /**
     * Domain
     */
    protected transient DataChannelDescriptor domain;

    /**
     * Created entity cells. Maps to entity name, since GraphBuilder can be serialized
     */
    protected Map<String, DefaultGraphCell> entityCells;

    /**
     * Created relationship cells Maps to relationship qualified name, since GraphBuilder
     * can be serialized
     */
    protected Map<String, DefaultEdge> relCells;

    /**
     * Created non-isolated objects
     */
    protected List<DefaultGraphCell> createdObjects;

    /**
     * Current project controller
     */
    protected transient ProjectController mediator;

    protected transient Entity selectedEntity;

    transient JPopupMenu popup;

    boolean undoEventsDisabled;

    public synchronized void buildGraph(
            ProjectController mediator,
            DataChannelDescriptor domain,
            boolean doLayout) {
        if (graph != null) {
            // graph already built, exiting silently
            return;
        }

        graph = new JGraph();
        GraphModel model = new DefaultGraphModel();
        graph.setModel(model);

        setProjectController(mediator);
        setDataDomain(domain);

        GraphLayoutCache view = new GraphLayoutCache(model, new DefaultCellViewFactory());
        graph.setGraphLayoutCache(view);

        graph.addMouseListener(new MouseAdapter() {

            public void mouseReleased(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    Object selected = graph.getSelectionCell();
                    if (selected != null && selected instanceof DefaultGraphCell) {
                        Object userObject = ((DefaultGraphCell) selected).getUserObject();
                        if (userObject instanceof EntityCellMetadata) {
                            showPopup(e.getPoint(), ((EntityCellMetadata) userObject)
                                    .fetchEntity());
                        }
                    }
                }
            }
        });

        graph.addMouseWheelListener(new MouseWheelListener() {

            public void mouseWheelMoved(MouseWheelEvent e) {
                graph.setScale(graph.getScale()
                        / Math.pow(ZOOM_FACTOR, e.getWheelRotation()));
            }
        });

        entityCells = new HashMap<String, DefaultGraphCell>();
        createdObjects = new ArrayList<DefaultGraphCell>();
        relCells = new HashMap<String, DefaultEdge>();

        /**
         * an array for entities that are not connected to anyone. We add them separately
         * so that layout doesn't touch them
         */
        List<DefaultGraphCell> isolatedObjects = new ArrayList<DefaultGraphCell>();

        /**
         * 1. Add all entities
         */
        for (DataMap map : domain.getDataMaps()) {
            DefaultGraphCell mapCell = new DefaultGraphCell();
            createdObjects.add(mapCell);

            for (Entity entity : getEntities(map)) {
                DefaultGraphCell cell = createEntityCell(entity);

                // mapCell.add(cell);
                // cell.setParent(mapCell);

                List<DefaultGraphCell> array = !isIsolated(domain, entity)
                        ? createdObjects
                        : isolatedObjects;
                array.add(cell);
                array.add((DefaultGraphCell) cell.getChildAt(0)); // port
            }
        }

        /**
         * 2. Add all relationships
         */
        for (DataMap map : domain.getDataMaps()) {
            for (Entity entity : getEntities(map)) {
                DefaultGraphCell sourceCell = entityCells.get(entity.getName());

                postProcessEntity(entity, sourceCell);
            }
        }
        view.insert(createdObjects.toArray());

        if (doLayout) {
            JGraphFacade facade = new JGraphFacade(graph);

            JGraphOrganicLayout layout = new JGraphOrganicLayout();
            layout.setNodeDistributionCostFactor(5000000000000.0);
            layout.setEdgeLengthCostFactor(1000);
            layout.setEdgeCrossingCostFactor(1000000);
            layout.setOptimizeBorderLine(false);
            layout.setOptimizeEdgeDistance(false);

            // JGraphSimpleLayout layout = new
            // JGraphSimpleLayout(JGraphSimpleLayout.TYPE_TILT, 4000, 2000);
            layout.run(facade);
            Map nested = facade.createNestedMap(true, true); // Obtain a map of the
                                                             // resulting attribute
                                                             // changes from the facade

            edit(nested); // Apply the results to the actual graph
        }

        /**
         * Adding isolated objects
         *
         * We're placing them so that they will take maximum space in left top corner. The
         * sample order is below:
         *
         * 1 2 6 7... 3 5 8 ... 4 9... 10 ...
         */
        if (isolatedObjects.size() > 0) {
            int n = isolatedObjects.size() / 2; // number of isolated entities
            int x = (int) Math.ceil((Math.sqrt(1 + 8 * n) - 1) / 2); // side of triangle

            Dimension pref = graph.getPreferredSize();
            int dx = pref.width / 2 / x; // x-distance between entities
            int dy = pref.height / 2 / x; // y-distance between entities

            int posX = dx / 2;
            int posY = dy / 2;

            int row = 0;

            for (int isolatedIndex = 0; isolatedIndex < isolatedObjects.size();) {
                for (int i = 0; isolatedIndex < isolatedObjects.size() && i < x - row; i++) {
                    GraphConstants.setBounds(isolatedObjects
                            .get(isolatedIndex)
                            .getAttributes(), new Rectangle2D.Double(
                            pref.width - posX,
                            pref.height - 3 * posY / 2,
                            10,
                            10));
                    isolatedIndex += 2; // because every 2nd object is port
                    posX += dx;
                }
                posX = dx / 2;
                posY += dy / 2;
                row++;
            }
        }

        view.insert(isolatedObjects.toArray());
        graph.getModel().addUndoableEditListener(this);
    }

    protected DefaultGraphCell createEntityCell(Entity entity) {
        DefaultGraphCell cell = new DefaultGraphCell(getCellMetadata(entity));

        GraphConstants.setResize(cell.getAttributes(), true);
        GraphConstants.setBorder(cell.getAttributes(), new LineBorder(Color.BLACK));

        GraphConstants.setEditable(cell.getAttributes(), false);
        entityCells.put(entity.getName(), cell);

        cell.addPort();
        return cell;
    }

    public DefaultGraphCell getEntityCell(String entityName) {
        return entityCells.get(entityName);
    }

    /**
     * Post (i.e. after creation on entity cell) process of the entity
     */
    protected void postProcessEntity(Entity entity, DefaultGraphCell cell) {
        for (Relationship rel : entity.getRelationships()) {
            if (rel.getSourceEntity() != null && rel.getTargetEntity() != null) {
                DefaultEdge edge = createRelationshipCell(rel);
                if (edge != null) {
                    createdObjects.add(edge);
                }
            }
        }
    }

    /**
     * Returns whether an entity is not connected to any other TODO: not fine algorithm,
     * it iterates through all entities and all rels
     */
    protected boolean isIsolated(DataChannelDescriptor domain, Entity entity) {
        if (entity.getRelationships().size() == 0) {
            // searching for rels that have a target="entity"

            for (DataMap map : domain.getDataMaps()) {
                for (Entity source : getEntities(map)) {
                    if (source.getAnyRelationship(entity) != null) {
                        return false;
                    }
                }
            }
            return true;
        }
        return false;
    }

    protected abstract Collection<? extends Entity> getEntities(DataMap map);

    /**
     * Returns label for relationship on the graph, considering its "mandatory" and
     * "to-many" properties
     */
    private static String getRelationshipLabel(Relationship rel) {
        if (rel == null) {
            return null;
        }

        if (rel.isToMany()) {
            return "0..*";
        }
        return rel.isMandatory() ? "1" : "0..1";
    }

    /**
     * Returns metadata (user object) for this cell
     */
    protected abstract EntityCellMetadata getCellMetadata(Entity e);

    protected void showPopup(Point p, Entity entity) {
        selectedEntity = entity;
        if (popup == null) {
            popup = createPopupMenu();
        }
        popup.show(graph, p.x, p.y);
    }

    public Entity getSelectedEntity() {
        return selectedEntity;
    }

    /**
     * Creates popup menu
     */
    protected JPopupMenu createPopupMenu() {
        ActionManager actionManager = Application.getInstance().getActionManager();

        JPopupMenu menu = new JPopupMenu();
        menu.add(new EntityDisplayAction(this).buildMenu());
        menu.addSeparator();
        menu.add(new EntityDisplayAction(this, actionManager
                .getAction(CreateAttributeAction.class)).buildMenu());
        menu.add(new EntityDisplayAction(this, actionManager
                .getAction(CreateRelationshipAction.class)).buildMenu());
        menu.addSeparator();
        menu.add(new RemoveEntityAction(this));

        return menu;
    }

    /**
     * Updates specified entity on the graph
     */
    protected void updateEntityCell(Entity e) {
        DefaultGraphCell cell = entityCells.get(e.getName());
        if (cell != null) {
            GraphConstants.setValue(cell.getAttributes(), getCellMetadata(e));
            GraphConstants.setResize(cell.getAttributes(), true);

            Map nested = new HashMap();
            nested.put(cell, cell.getAttributes());

            edit(nested);
        }
    }

    protected void updateRelationshipCell(Relationship rel) {
        if (rel.getSourceEntity() != null && rel.getTargetEntity() != null) {
            DefaultEdge edge = relCells.get(getQualifiedName(rel));
            if (edge != null) {
                updateRelationshipLabels(edge, rel, rel.getReverseRelationship());

                Map nested = new HashMap();
                nested.put(edge, edge.getAttributes());
                edit(nested);
            }
            else {
                insertRelationshipCell(rel);
            }
        }
    }

    protected void removeEntityCell(Entity e) {
        final DefaultGraphCell cell = entityCells.get(e.getName());
        if (cell != null) {
            runWithUndoDisabled(new Runnable() {

                public void run() {
                    graph.getGraphLayoutCache().remove(new Object[] {
                        cell
                    }, true, true);
                }
            });
            entityCells.remove(e.getName());
        }
    }

    protected void removeRelationshipCell(Relationship rel) {
        final DefaultEdge edge = relCells.get(getQualifiedName(rel));
        if (edge != null) {
            runWithUndoDisabled(new Runnable() {

                public void run() {
                    graph.getGraphLayoutCache().remove(new Object[] {
                        edge
                    });
                }
            });
            relCells.remove(getQualifiedName(rel));
        }
    }

    protected DefaultEdge createRelationshipCell(Relationship rel) {
        if (!relCells.containsKey(getQualifiedName(rel))) {
            Relationship reverse = rel.getReverseRelationship();

            DefaultEdge edge = new DefaultEdge();

            // GraphConstants.setLineStyle(edge.getAttributes(),
            // GraphConstants.STYLE_ORTHOGONAL);
            // GraphConstants.setRouting(edge.getAttributes(),
            // GraphConstants.ROUTING_SIMPLE);

            GraphConstants.setEditable(edge.getAttributes(), false);
            GraphConstants.setLabelAlongEdge(edge.getAttributes(), true);
            GraphConstants.setSelectable(edge.getAttributes(), false);
            GraphConstants.setFont(edge.getAttributes(), EDGE_FONT);

            updateRelationshipLabels(edge, rel, reverse);

            relCells.put(getQualifiedName(rel), edge);

            if (reverse != null) {
                relCells.put(getQualifiedName(reverse), edge);
            }

            return edge;
        }
        return null;
    }

    protected void insertRelationshipCell(Relationship rel) {
        DefaultEdge edge = createRelationshipCell(rel);
        insert(edge);
    }

    protected void insertEntityCell(Entity entity) {
        DefaultGraphCell cell = createEntityCell(entity);

        // putting cell to a random posistion..
        GraphConstants.setBounds(cell.getAttributes(), new Rectangle2D.Double(Math
                .random()
                * graph.getWidth(), Math.random() * graph.getHeight(), 10, 10));

        // setting graph type-specific attrs
        postProcessEntity(entity, cell);

        insert(cell);
    }

    /**
     * Updates relationship labels for specified relationship edge.
     *
     * @param order order of relationship in entity's same target relationships - to
     *            differ labels of relationships with same source and target
     */
    protected void updateRelationshipLabels(
            DefaultEdge edge,
            Relationship rel,
            Relationship reverse) {
        DefaultGraphCell sourceCell = entityCells.get(rel.getSourceEntity().getName());
        DefaultGraphCell targetCell = entityCells.get(rel.getTargetEntity().getName());

        edge.setSource(sourceCell != null ? sourceCell.getChildAt(0) : null);
        edge.setTarget(targetCell != null ? targetCell.getChildAt(0) : null);

        Object[] labels = {
                rel.getName() + " " + getRelationshipLabel(rel),
                reverse == null ? "" : reverse.getName()
                        + " "
                        + getRelationshipLabel(reverse)
        };
        GraphConstants.setExtraLabels(edge.getAttributes(), labels);

        Point2D[] labelPositions = {
                new Point2D.Double(
                        GraphConstants.PERMILLE * (0.1 + 0.2 * Math.random()),
                        10),
                new Point2D.Double(
                        GraphConstants.PERMILLE * (0.9 - 0.2 * Math.random()),
                        -10)
        };
        GraphConstants.setExtraLabelPositions(edge.getAttributes(), labelPositions);
    }

    public JGraph getGraph() {
        return graph;
    }

    public void dataMapAdded(DataMapEvent e) {
    }

    public void dataMapChanged(DataMapEvent e) {
    }

    public void dataMapRemoved(DataMapEvent e) {
        for (Entity entity : getEntities(e.getDataMap())) {
            removeEntityCell(entity);
        }
    }

    public void setProjectController(ProjectController mediator) {
        this.mediator = mediator;

        mediator.addDataMapListener(this);
    }

    public void setDataDomain(DataChannelDescriptor domain) {
        this.domain = domain;
    }

    public DataChannelDescriptor getDataDomain() {
        return domain;
    }

    public void destroy() {
        mediator.removeDataMapListener(this);
    }

    /**
     * Checks if entity name has changed, then changes map key
     */
    protected void remapEntity(EntityEvent e) {
        if (e.isNameChange()) {
            entityCells.put(e.getNewName(), entityCells.remove(e.getOldName()));
        }
    }

    /**
     * Checks if entity name has changed, then changes map key
     */
    protected void remapRelationship(RelationshipEvent e) {
        if (e.isNameChange()) {
            relCells.put(getQualifiedName(e.getRelationship()), relCells.remove(e
                    .getEntity()
                    .getName()
                    + "."
                    + e.getOldName()));
        }
    }

    /**
     * Returns qualified name (entity name + relationship name) for a relationship
     */
    static String getQualifiedName(Relationship rel) {
        return rel.getSourceEntity().getName() + "." + rel.getName();
    }

    public void encodeAsXML(XMLEncoder encoder) {
        encoder.print("<graph type=\"");
        encoder.print(getType().toString());
        encoder.print("\" scale=\"");
        encoder.print(String.valueOf(graph.getScale()));
        encoder.println("\">");
        encoder.indent(1);

        for (Entry<String, DefaultGraphCell> entry : entityCells.entrySet()) {
            encoder.print("<entity name=\"");
            encoder.print(entry.getKey());
            encoder.print("\" ");

            DefaultGraphCell cell = entry.getValue();
            Rectangle2D rect = graph.getCellBounds(cell);
            encodeRecangle(encoder, rect);
            encoder.println("/>");
        }

        encoder.indent(-1);
        encoder.println("</graph>");
    }

    private void encodeRecangle(XMLEncoder encoder, Rectangle2D rect) {
        encoder.print("x=\"");
        encoder.print(rect.getX() + "\" y=\"");
        encoder.print(rect.getY() + "\" width=\"");
        encoder.print(rect.getWidth() + "\" height=\"");
        encoder.print(rect.getHeight() + "\" ");
    }

    private synchronized void edit(final Map map) {
        runWithUndoDisabled(new Runnable() {

            public void run() {
                graph.getGraphLayoutCache().edit(map);
            }
        });
    }

    private synchronized void insert(final Object cell) {
        runWithUndoDisabled(new Runnable() {

            public void run() {
                graph.getGraphLayoutCache().insert(cell);
            }
        });
    }

    private synchronized void runWithUndoDisabled(Runnable r) {
        undoEventsDisabled = true;
        try {
            r.run();
        }
        finally {
            undoEventsDisabled = false;
        }
    }

    public void undoableEditHappened(UndoableEditEvent e) {
        if (!undoEventsDisabled) {
            // graph has been modified
            mediator.setDirty(true);

            Application.getInstance().getUndoManager().undoableEditHappened(e);
        }
    }
}
TOP

Related Classes of org.apache.cayenne.modeler.graph.BaseGraphBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.