/****************************************************************************** * Copyright: GPL v3 * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * ******************************************************************************/ package dba.gui.auxClasses.jGraph; import com.mxgraph.model.mxCell; import com.mxgraph.model.mxGeometry; import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.util.mxPoint; import com.mxgraph.view.mxGraph; import dba.utils.ImageSize; import dbaCore.data.Attribute; import dbaCore.data.ForeignKeyConstraint; import dbaCore.data.RelationSchema; import javax.swing.*; import java.util.ArrayList; import java.util.List; /** * Updates / Draws a given RelationView-graph */ public class RelationGraphUpdater extends RelationUpdater { private mxGraph graph; private mxGraphComponent graphComponent; private Object parentPane; private ArrayList<RelationSchema> dbRelations; private ArrayList<ForeignKeyConstraint> foreignKeys; public RelationGraphUpdater(mxGraph graph, mxGraphComponent graphComponent, ArrayList<RelationSchema> relations, ArrayList<ForeignKeyConstraint> foreignKeys) { super(); dbRelations = relations; this.foreignKeys = foreignKeys; parentPane = graph.getDefaultParent(); this.graphComponent = graphComponent; this.graph = graph; graph.setAutoOrigin(true); graph.setAutoSizeCells(true); } public void run() { graph.getModel().beginUpdate(); try { display(); } finally { graph.getModel().endUpdate(); } } /** * Removes all Cells from the graph */ private void removeAllRelations() { graph.removeCells(graph.getChildVertices(graph.getDefaultParent())); } /** * Returns a ArrayList containing all relations that participate in * a foreignKey-constraint * * @return ArrayList of relations involved in FK-Constraint */ private ArrayList<String> getFkInvolvedRelations() { ArrayList<String> involvedRelations = new ArrayList<>(); for (ForeignKeyConstraint fk : foreignKeys) { if (!involvedRelations.contains(fk.getSourceRelationName())) { involvedRelations.add(fk.getSourceRelationName()); } if (!involvedRelations.contains(fk.getTargetRelationName())) { involvedRelations.add(fk.getTargetRelationName()); } } return involvedRelations; } /** * Returns the y-coordinate of the outermost south cell * * @return the lowest y-coordinate of the graph */ private int getLowestCellPoint() { int lowestCellPoint = 0; int cellPoint; mxCell cell; for (Object obj : graph.getChildVertices(graph.getDefaultParent())) { cell = (mxCell) obj; cellPoint = cell.getGeometry().getPoint().y + (int) cell.getGeometry().getHeight(); if (cellPoint > lowestCellPoint) { lowestCellPoint = cellPoint; } } return lowestCellPoint; } /** * Displays all given relations */ private void display() { // offset of 40 to compensate Header ArrayList<String> fkRelations = getFkInvolvedRelations(); int offset = 40; mxCell relationCell; removeAllRelations(); // add relations that occur in a foreignKey constraint for (RelationSchema relation : dbRelations) { if (fkRelations.contains(relation.getName())) { relationCell = (mxCell) insertRelation(graph, relation, offset); offset += relationCell.getGeometry().getHeight() + 20; } } // add foreign key Edges insertForeignKeyEdges(); // update Layout in order to display fk-constraints nice updateLayout(graphComponent); // add the rest of the relations which don't occur in a foreignKey // constraint int lowestPoint = getLowestCellPoint(); offset = lowestPoint != 0 ? lowestPoint + 25 : 0; for (RelationSchema relation : dbRelations) { if (!fkRelations.contains(relation.getName())) { relationCell = (mxCell) insertRelation(graph, relation, offset); offset += relationCell.getGeometry().getHeight() + 20; } } } /** * Inserts a given Relation in a graph * * @param graph the graph as target for insertion * @param relation the relation that should be inserted * @param offset the vertical ofset of the relation * @return the mxCell representing the Relation */ private Object insertRelation(mxGraph graph, RelationSchema relation, int offset) { // Compensate big header int attributeOffset = 40; int width = relation.getName().length() * 15; ImageSize optimalImageSize = getImageSize(relation); mxCell relationVertex = (mxCell) graph.insertVertex(parentPane, relation.getName(), relation, 0, offset, width, 40 + 1 + relation.getAttributes().size() * 25, "RELATION"); double maxWidth = width; // Add attributes mxGeometry geo; for (Attribute attr : relation.getAttributes()) { mxCell attributeCell = (mxCell) graph.insertVertex(relationVertex, attr.getName(), attr, 1, attributeOffset, width - 2, 25, getAttributeStyle(attr, optimalImageSize)); graph.updateCellSize(attributeCell); geo = attributeCell.getGeometry(); if (geo.getWidth() > maxWidth) { maxWidth = geo.getWidth(); } attributeOffset += 25; } maxWidth += 5; geo = relationVertex.getGeometry(); geo.setWidth(maxWidth); for (Object child : graph.getChildVertices(relationVertex)) { if (child instanceof mxCell) { mxCell cell = (mxCell) child; geo = cell.getGeometry(); geo.setWidth(maxWidth - 2); geo.setHeight(25); } } return relationVertex; } protected ImageSize getImageSize(RelationSchema relation) { int pkANDfk = 0; int pkORfk = 0; for (Attribute attribute : relation.getAttributes()) { if (attribute.getIsPrimaryKey() && attribute.getIsForeignKey()) { pkANDfk++; } else if (attribute.getIsPrimaryKey() || attribute.getIsForeignKey()) { pkORfk++; } } if (pkANDfk > 0) { return ImageSize.BIG; } else if (pkORfk > 0) { return ImageSize.SMALL; } else { return ImageSize.NO; } } /** * Resets the actual Layout of the graph * * @param graphComponent component */ private void updateLayout(mxGraphComponent graphComponent) { HierarchicalRelationLayout layout = new HierarchicalRelationLayout(graph, SwingConstants.WEST); layout.setDisableEdgeStyle(false); //Use the specified EdgeStyle Object cell = graphComponent.getGraph().getDefaultParent(); layout.execute(cell); } /** * Inserts all Relations that participate in a FK-Constraint */ private void insertForeignKeyEdges() { ArrayList<mxCell> fkCells; if (foreignKeys == null) { return; } for (ForeignKeyConstraint fk : foreignKeys) { // Relations fkCells = findFkRelationCells(fk); insertFkEdge(fkCells, false); // Attributes fkCells = findFkAttributeCells(fk, fkCells); insertFkEdge(fkCells, true); } } /** * Inserts a edge between given cells * * @param fkCells the cells to connect by a edge * @param visible determines if the edge should be visible */ private void insertFkEdge(ArrayList<mxCell> fkCells, boolean visible) { if (fkCells.size() == 2) { if (visible) { mxCell cell = (mxCell) graph.insertEdge(parentPane, null, fkCells.get(0).getValue(), fkCells.get(0), fkCells.get(1), "FK_ARROW"); handleSelfReference(cell, fkCells.get(0), fkCells.get(1)); } else { graph.insertEdge(parentPane, null, "", fkCells.get(0), fkCells.get(1), "INVISIBLE_EDGE"); } } } /** * Moves a Edge that references the same Relation outside of the Relation-Area * * @param edge the ForeignKey-mxCell to work with * @param firstCell the first cell to work with * @param secondCell the second cell to work with */ private void handleSelfReference(mxCell edge, mxCell firstCell, mxCell secondCell) { if (firstCell.getParent() == secondCell.getParent()) { mxGeometry edgeGeo = graph.getModel().getGeometry(edge); List<mxPoint> points = edgeGeo.getPoints(); if (points == null) { points = new ArrayList<>(); } points.add(new mxPoint(edgeGeo.getX() + graph.getModel().getGeometry(firstCell).getWidth() + 20, graph.getModel().getGeometry(firstCell).getCenterY())); points.add(new mxPoint(edgeGeo.getX() + graph.getModel().getGeometry(firstCell).getWidth() + 20, graph.getModel().getGeometry(secondCell).getCenterY())); edgeGeo.setPoints(points); graph.getModel().setGeometry(edge, edgeGeo); } } /** * Returns the Attribute-Cells of the given name * * @param fk the ForeignKeyConstraint with the name of the values * @param relationCells the RelationCells containing the Attributes * @return a ArrayList containing a Source- and a TargetAttribute */ private ArrayList<mxCell> findFkAttributeCells(ForeignKeyConstraint fk, ArrayList<mxCell> relationCells) { ArrayList<mxCell> fkCells = new ArrayList<>(); if (relationCells.size() == 2) { mxCell sourceCell; mxCell targetCell; sourceCell = getAttributeCell(relationCells.get(0), fk.getSourceAttributeName()); targetCell = getAttributeCell(relationCells.get(1), fk.getTargetAttributeName()); if (sourceCell != null) { fkCells.add(sourceCell); } if (targetCell != null) { fkCells.add(targetCell); } } return fkCells; } /** * Returns a Attribute of a Relation by name * * @param relationCell the parent - RelationCell * @param attributeName the name of the Attribute to look for * @return the cell representing the given attribute */ private mxCell getAttributeCell(mxCell relationCell, String attributeName) { mxCell resultCell = null; mxCell cell; for (int index = 0; index < relationCell.getChildCount(); index++) { cell = (mxCell) relationCell.getChildAt(index); if (cell.getValue() instanceof Attribute) { if (((Attribute) cell.getValue()).getName().equals(attributeName)) { resultCell = cell; } } } return resultCell; } /** * Returns the Relation-Cells of the given name * * @param fk the fk with the names of the relations * @return A ArrayList containing the two relation of the FK */ private ArrayList<mxCell> findFkRelationCells(ForeignKeyConstraint fk) { ArrayList<mxCell> fkCells = new ArrayList<>(); String relationName; mxCell cell; mxCell sourceCell = null; mxCell targetCell = null; for (Object obj : graph.getChildVertices(graph.getDefaultParent())) { if (sourceCell != null && targetCell != null) { break; } cell = (mxCell) obj; if (cell.getValue() instanceof RelationSchema) { relationName = ((RelationSchema) cell.getValue()).getName(); if (relationName.equals(fk.getSourceRelationName())) { sourceCell = cell; } if (relationName.equals(fk.getTargetRelationName())) { targetCell = cell; } } } if (sourceCell != null) { fkCells.add(sourceCell); } if (targetCell != null) { fkCells.add(targetCell); } return fkCells; } }