Added better stuff and new model.

This commit is contained in:
Andrew Lalis 2021-02-07 22:28:51 +01:00
parent db140733ae
commit 7e22602b57
16 changed files with 136 additions and 30 deletions

View File

@ -1,2 +1,12 @@
# EntityRelationMappingEditor # Entity-Relation Mapping Editor
Simple GUI tool to create entity-relational mapping diagrams. A simple UI for editing entity-relation mapping diagrams.
## How to Use
The interface and menus should be pretty self-explanatory, but here are some tips to get you started:
* Click on a relation to select it.
* You can CTRL + click to select multiple relations.
* Drag the relations to move them around in the window.
* Right-click on different areas to access context menus with some helpful actions.
* When exporting the model to an image, be sure to add the desired image file extension (`.png`, `.jpg`, `.bmp`, etc.), or the application will default to `.png`.

BIN
design/main_interface.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -0,0 +1,35 @@
package nl.andrewlalis.erme.control.actions;
import lombok.Setter;
import nl.andrewlalis.erme.model.MappingModel;
import nl.andrewlalis.erme.view.DiagramPanel;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
public class NewModelAction extends AbstractAction {
private static NewModelAction instance;
public static NewModelAction getInstance() {
if (instance == null) {
instance = new NewModelAction();
}
return instance;
}
@Setter
private DiagramPanel diagramPanel;
public NewModelAction() {
super("New Model");
this.putValue(SHORT_DESCRIPTION, "Create a new model.");
this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK));
}
@Override
public void actionPerformed(ActionEvent e) {
this.diagramPanel.setModel(new MappingModel());
}
}

View File

@ -65,7 +65,13 @@ public class AddAttributeAction extends AbstractAction {
AttributeType.values(), AttributeType.values(),
AttributeType.PLAIN AttributeType.PLAIN
); );
if (type.equals(AttributeType.FOREIGN_KEY)) { boolean shouldUseForeignKey = JOptionPane.showConfirmDialog(
c,
"Is this attribute a foreign key?",
"Foreign Key",
JOptionPane.YES_NO_OPTION
) == JOptionPane.YES_OPTION;
if (shouldUseForeignKey) {
if (this.model.getRelations().size() < 2) { if (this.model.getRelations().size() < 2) {
JOptionPane.showMessageDialog(c, "There should be at least 2 relations present in the model.", "Not Enough Relations", JOptionPane.WARNING_MESSAGE); JOptionPane.showMessageDialog(c, "There should be at least 2 relations present in the model.", "Not Enough Relations", JOptionPane.WARNING_MESSAGE);
return; return;
@ -94,7 +100,7 @@ public class AddAttributeAction extends AbstractAction {
eligibleAttributes.get(0) eligibleAttributes.get(0)
); );
if (fkAttribute != null) { if (fkAttribute != null) {
r.addAttribute(new ForeignKeyAttribute(r, name, fkAttribute)); r.addAttribute(new ForeignKeyAttribute(r, type, name, fkAttribute), index);
} }
} else { } else {
r.addAttribute(new Attribute(r, type, name), index); r.addAttribute(new Attribute(r, type, name), index);

View File

@ -47,8 +47,8 @@ public class RemoveAttributeAction extends AbstractAction {
Relation r = selectedRelations.get(0); Relation r = selectedRelations.get(0);
Attribute attribute = (Attribute) JOptionPane.showInputDialog( Attribute attribute = (Attribute) JOptionPane.showInputDialog(
(Component) e.getSource(), (Component) e.getSource(),
"Select the index to insert this attribute at.", "Select the attribute to remove.",
"Attribute Index", "Select Attribute",
JOptionPane.PLAIN_MESSAGE, JOptionPane.PLAIN_MESSAGE,
null, null,
r.getAttributes().toArray(new Attribute[0]), r.getAttributes().toArray(new Attribute[0]),

View File

@ -46,6 +46,7 @@ public class Attribute implements Serializable {
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Attribute attribute = (Attribute) o; Attribute attribute = (Attribute) o;
return type == attribute.type && return type == attribute.type &&
relation.equals(attribute.getRelation()) &&
name.equals(attribute.name); name.equals(attribute.name);
} }
@ -56,6 +57,6 @@ public class Attribute implements Serializable {
@Override @Override
public String toString() { public String toString() {
return this.getName() + ":" + this.getType().name(); return this.getName();
} }
} }

View File

@ -3,6 +3,5 @@ package nl.andrewlalis.erme.model;
public enum AttributeType { public enum AttributeType {
PLAIN, PLAIN,
ID_KEY, ID_KEY,
PARTIAL_ID_KEY, PARTIAL_ID_KEY
FOREIGN_KEY
} }

View File

@ -6,13 +6,13 @@ import lombok.Getter;
public class ForeignKeyAttribute extends Attribute { public class ForeignKeyAttribute extends Attribute {
private Attribute reference; private Attribute reference;
public ForeignKeyAttribute(Relation relation, String name, Attribute reference) { public ForeignKeyAttribute(Relation relation, AttributeType type, String name, Attribute reference) {
super(relation, AttributeType.FOREIGN_KEY, name); super(relation, type, name);
this.reference = reference; this.reference = reference;
} }
public ForeignKeyAttribute(Relation relation, String name, String referencedRelationName, String referencedAttributeName) { public ForeignKeyAttribute(Relation relation, AttributeType type, String name, String referencedRelationName, String referencedAttributeName) {
this(relation, name, relation.getModel().findAttribute(referencedRelationName, referencedAttributeName)); this(relation, type, name, relation.getModel().findAttribute(referencedRelationName, referencedAttributeName));
if (this.getReference() == null) { if (this.getReference() == null) {
throw new IllegalArgumentException("Unknown attribute name."); throw new IllegalArgumentException("Unknown attribute name.");
} }
@ -22,4 +22,13 @@ public class ForeignKeyAttribute extends Attribute {
this.reference = reference; this.reference = reference;
this.getRelation().getModel().fireChangedEvent(); this.getRelation().getModel().fireChangedEvent();
} }
public String getFullReferenceName() {
return this.getReference().getRelation().getName() + "." + this.getReference().getName();
}
@Override
public String toString() {
return super.toString() + "->" + this.getFullReferenceName();
}
} }

View File

@ -52,6 +52,21 @@ public class MappingModel implements Serializable {
return null; return null;
} }
public void removeAllReferencingAttributes(Attribute referenced) {
for (Relation r : this.getRelations()) {
Set<Attribute> removalSet = new HashSet<>();
for (Attribute a : r.getAttributes()) {
if (a instanceof ForeignKeyAttribute) {
ForeignKeyAttribute fkA = (ForeignKeyAttribute) a;
if (fkA.getReference().equals(referenced)) {
removalSet.add(fkA);
}
}
}
removalSet.forEach(r::removeAttribute);
}
}
public void addChangeListener(ModelChangeListener listener) { public void addChangeListener(ModelChangeListener listener) {
if (this.changeListeners == null) { if (this.changeListeners == null) {
this.changeListeners = new HashSet<>(); this.changeListeners = new HashSet<>();

View File

@ -54,6 +54,7 @@ public class Relation implements Serializable {
public void removeAttribute(Attribute attribute) { public void removeAttribute(Attribute attribute) {
if (this.attributes.remove(attribute)) { if (this.attributes.remove(attribute)) {
this.model.removeAllReferencingAttributes(attribute);
this.model.fireChangedEvent(); this.model.fireChangedEvent();
} }
} }
@ -83,4 +84,9 @@ public class Relation implements Serializable {
public int hashCode() { public int hashCode() {
return this.getName().hashCode(); return this.getName().hashCode();
} }
@Override
public String toString() {
return this.getName();
}
} }

View File

@ -3,6 +3,7 @@ package nl.andrewlalis.erme.view;
import lombok.Getter; import lombok.Getter;
import nl.andrewlalis.erme.control.actions.ExportToImageAction; import nl.andrewlalis.erme.control.actions.ExportToImageAction;
import nl.andrewlalis.erme.control.actions.LoadAction; import nl.andrewlalis.erme.control.actions.LoadAction;
import nl.andrewlalis.erme.control.actions.NewModelAction;
import nl.andrewlalis.erme.control.actions.SaveAction; import nl.andrewlalis.erme.control.actions.SaveAction;
import nl.andrewlalis.erme.control.actions.edits.AddAttributeAction; import nl.andrewlalis.erme.control.actions.edits.AddAttributeAction;
import nl.andrewlalis.erme.control.actions.edits.AddRelationAction; import nl.andrewlalis.erme.control.actions.edits.AddRelationAction;
@ -74,6 +75,7 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
* Updates all the action singletons with the latest model information. * Updates all the action singletons with the latest model information.
*/ */
private void updateActionModels() { private void updateActionModels() {
NewModelAction.getInstance().setDiagramPanel(this);
SaveAction.getInstance().setModel(this.model); SaveAction.getInstance().setModel(this.model);
LoadAction.getInstance().setDiagramPanel(this); LoadAction.getInstance().setDiagramPanel(this);
ExportToImageAction.getInstance().setModel(this.model); ExportToImageAction.getInstance().setModel(this.model);

View File

@ -16,7 +16,6 @@ public class DiagramPopupMenu extends JPopupMenu {
List<Relation> selectedRelations = model.getSelectedRelations(); List<Relation> selectedRelations = model.getSelectedRelations();
if (selectedRelations.size() == 0) { if (selectedRelations.size() == 0) {
this.add(AddRelationAction.getInstance()); this.add(AddRelationAction.getInstance());
this.add(ExportToImageAction.getInstance());
} }
if (selectedRelations.size() > 0) { if (selectedRelations.size() > 0) {
this.add(RemoveRelationAction.getInstance()); this.add(RemoveRelationAction.getInstance());

View File

@ -21,7 +21,7 @@ public class EditorFrame extends JFrame {
model.addRelation(usersRelation); model.addRelation(usersRelation);
Relation tokensRelation = new Relation(model, new Point(50, 120), "Tokens"); Relation tokensRelation = new Relation(model, new Point(50, 120), "Tokens");
tokensRelation.addAttribute(new Attribute(tokensRelation, AttributeType.ID_KEY, "tokenCode")); tokensRelation.addAttribute(new Attribute(tokensRelation, AttributeType.ID_KEY, "tokenCode"));
tokensRelation.addAttribute(new ForeignKeyAttribute(tokensRelation,"username", "Users", "username")); tokensRelation.addAttribute(new ForeignKeyAttribute(tokensRelation, AttributeType.PLAIN, "username", "Users", "username"));
tokensRelation.addAttribute(new Attribute(tokensRelation, AttributeType.PLAIN, "expirationDate")); tokensRelation.addAttribute(new Attribute(tokensRelation, AttributeType.PLAIN, "expirationDate"));
model.addRelation(tokensRelation); model.addRelation(tokensRelation);

View File

@ -15,12 +15,15 @@ public class EditorMenuBar extends JMenuBar {
public EditorMenuBar() { public EditorMenuBar() {
this.add(this.buildFileMenu()); this.add(this.buildFileMenu());
this.add(this.buildEditMenu()); this.add(this.buildEditMenu());
this.add(this.buildHelpMenu());
} }
private JMenu buildFileMenu() { private JMenu buildFileMenu() {
JMenu menu = new JMenu("File"); JMenu menu = new JMenu("File");
menu.add(NewModelAction.getInstance());
menu.add(SaveAction.getInstance()); menu.add(SaveAction.getInstance());
menu.add(LoadAction.getInstance()); menu.add(LoadAction.getInstance());
menu.addSeparator();
menu.add(ExportToImageAction.getInstance()); menu.add(ExportToImageAction.getInstance());
menu.addSeparator(); menu.addSeparator();
menu.add(ExitAction.getInstance()); menu.add(ExitAction.getInstance());
@ -38,4 +41,10 @@ public class EditorMenuBar extends JMenuBar {
menu.add(RedoAction.getInstance()); menu.add(RedoAction.getInstance());
return menu; return menu;
} }
private JMenu buildHelpMenu() {
JMenu menu = new JMenu("Help");
menu.add("About");
return menu;
}
} }

View File

@ -2,6 +2,7 @@ package nl.andrewlalis.erme.view.view_models;
import nl.andrewlalis.erme.model.Attribute; import nl.andrewlalis.erme.model.Attribute;
import nl.andrewlalis.erme.model.AttributeType; import nl.andrewlalis.erme.model.AttributeType;
import nl.andrewlalis.erme.model.ForeignKeyAttribute;
import java.awt.*; import java.awt.*;
import java.awt.font.TextAttribute; import java.awt.font.TextAttribute;
@ -13,6 +14,7 @@ public class AttributeViewModel implements ViewModel {
public static final int PADDING_Y = 5; public static final int PADDING_Y = 5;
public static final Color BACKGROUND_COLOR = Color.LIGHT_GRAY; public static final Color BACKGROUND_COLOR = Color.LIGHT_GRAY;
public static final Color FONT_COLOR = Color.BLACK; public static final Color FONT_COLOR = Color.BLACK;
public static final float FK_FONT_SIZE = 11.0f;
private final Attribute attribute; private final Attribute attribute;
@ -23,33 +25,45 @@ public class AttributeViewModel implements ViewModel {
@Override @Override
public void draw(Graphics2D g) { public void draw(Graphics2D g) {
AttributedString as = this.getAttributedString(g); AttributedString as = this.getAttributedString(g);
Rectangle r = this.getBounds(g, as); Rectangle r = this.getBoxBounds(g, as);
g.setColor(BACKGROUND_COLOR); g.setColor(BACKGROUND_COLOR);
g.fillRect(r.x, r.y, r.width, r.height); g.fillRect(r.x, r.y, r.width, r.height);
g.setColor(FONT_COLOR); g.setColor(FONT_COLOR);
g.drawRect(r.x, r.y, r.width, r.height); g.drawRect(r.x, r.y, r.width, r.height);
g.drawString(as.getIterator(), r.x + PADDING_X, r.y + (r.height - PADDING_Y)); g.drawString(as.getIterator(), r.x + PADDING_X, r.y + (r.height - PADDING_Y));
if (this.attribute instanceof ForeignKeyAttribute) {
ForeignKeyAttribute fkAttribute = (ForeignKeyAttribute) this.attribute;
Font originalFont = g.getFont();
g.setFont(g.getFont().deriveFont(Font.ITALIC, FK_FONT_SIZE));
g.drawString(fkAttribute.getFullReferenceName(), r.x + PADDING_X, r.y - PADDING_Y);
g.setFont(originalFont);
}
} }
private Rectangle getBounds(Graphics2D g, AttributedString as) { private Rectangle getBoxBounds(Graphics2D g, AttributedString as) {
int x = this.attribute.getRelation().getPosition().x + RelationViewModel.PADDING_X; int x = this.attribute.getRelation().getPosition().x + RelationViewModel.PADDING_X;
int y = this.attribute.getRelation().getPosition().y + this.attribute.getRelation().getViewModel().getNameBounds(g).height + PADDING_Y; int y = this.attribute.getRelation().getPosition().y + this.attribute.getRelation().getViewModel().getNameBounds(g).height + RelationViewModel.ATTRIBUTE_SEPARATION;
int i = 0; int i = 0;
while (!this.attribute.getRelation().getAttributes().get(i).equals(this.attribute)) { while (!this.attribute.getRelation().getAttributes().get(i).equals(this.attribute)) {
x += g.getFontMetrics().stringWidth(this.attribute.getRelation().getAttributes().get(i).getName()) + (2 * PADDING_X); x += this.attribute.getRelation().getAttributes().get(i).getViewModel().getBoxBounds(g).width;
i++; i++;
} }
Rectangle2D rect = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.attribute.getName().length(), g); Rectangle2D nameRect = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.attribute.getName().length(), g);
return new Rectangle( int width = (int) nameRect.getWidth() + (2 * PADDING_X);
x, int height = (int) nameRect.getHeight() + (2 * PADDING_Y);
y, if (this.attribute instanceof ForeignKeyAttribute) {
(int) rect.getWidth() + (2 * PADDING_X), ForeignKeyAttribute fkAttribute = (ForeignKeyAttribute) this.attribute;
(int) rect.getHeight() + (2 * PADDING_Y) Font originalFont = g.getFont();
); g.setFont(g.getFont().deriveFont(Font.ITALIC, FK_FONT_SIZE));
Rectangle referenceNameBounds = g.getFontMetrics().getStringBounds(fkAttribute.getFullReferenceName(), g).getBounds();
g.setFont(originalFont);
width = Math.max(width, referenceNameBounds.width + (2 * PADDING_X));
}
return new Rectangle(x, y, width, height);
} }
public Rectangle getBounds(Graphics2D g) { public Rectangle getBoxBounds(Graphics2D g) {
return this.getBounds(g, this.getAttributedString(g)); return this.getBoxBounds(g, this.getAttributedString(g));
} }
private AttributedString getAttributedString(Graphics2D g) { private AttributedString getAttributedString(Graphics2D g) {

View File

@ -11,6 +11,7 @@ import java.text.AttributedString;
public class RelationViewModel implements ViewModel { public class RelationViewModel implements ViewModel {
public static final int PADDING_X = 5; public static final int PADDING_X = 5;
public static final int PADDING_Y = 5; public static final int PADDING_Y = 5;
public static final int ATTRIBUTE_SEPARATION = 10;
private final Relation relation; private final Relation relation;
@ -40,13 +41,13 @@ public class RelationViewModel implements ViewModel {
int totalAttributeWidth = 0; int totalAttributeWidth = 0;
int maxAttributeHeight = 0; int maxAttributeHeight = 0;
for (Attribute a : this.relation.getAttributes()) { for (Attribute a : this.relation.getAttributes()) {
Rectangle attributeBounds = a.getViewModel().getBounds(g); Rectangle attributeBounds = a.getViewModel().getBoxBounds(g);
totalAttributeWidth += attributeBounds.width; totalAttributeWidth += attributeBounds.width;
maxAttributeHeight = Math.max(maxAttributeHeight, attributeBounds.height); maxAttributeHeight = Math.max(maxAttributeHeight, attributeBounds.height);
} }
Rectangle nameBounds = this.getNameBounds(g); Rectangle nameBounds = this.getNameBounds(g);
rect.width = Math.max(totalAttributeWidth, nameBounds.width) + (2 * PADDING_X); rect.width = Math.max(totalAttributeWidth, nameBounds.width) + (2 * PADDING_X);
rect.height = nameBounds.height + maxAttributeHeight + (2 * PADDING_Y); rect.height = nameBounds.height + maxAttributeHeight + (2 * PADDING_Y) + ATTRIBUTE_SEPARATION;
return rect; return rect;
} }