Added better stuff and new model.
This commit is contained in:
parent
db140733ae
commit
7e22602b57
14
README.md
14
README.md
|
@ -1,2 +1,12 @@
|
|||
# EntityRelationMappingEditor
|
||||
Simple GUI tool to create entity-relational mapping diagrams.
|
||||
# Entity-Relation Mapping Editor
|
||||
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`.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 9.7 KiB |
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -65,7 +65,13 @@ public class AddAttributeAction extends AbstractAction {
|
|||
AttributeType.values(),
|
||||
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) {
|
||||
JOptionPane.showMessageDialog(c, "There should be at least 2 relations present in the model.", "Not Enough Relations", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
|
@ -94,7 +100,7 @@ public class AddAttributeAction extends AbstractAction {
|
|||
eligibleAttributes.get(0)
|
||||
);
|
||||
if (fkAttribute != null) {
|
||||
r.addAttribute(new ForeignKeyAttribute(r, name, fkAttribute));
|
||||
r.addAttribute(new ForeignKeyAttribute(r, type, name, fkAttribute), index);
|
||||
}
|
||||
} else {
|
||||
r.addAttribute(new Attribute(r, type, name), index);
|
||||
|
|
|
@ -47,8 +47,8 @@ public class RemoveAttributeAction extends AbstractAction {
|
|||
Relation r = selectedRelations.get(0);
|
||||
Attribute attribute = (Attribute) JOptionPane.showInputDialog(
|
||||
(Component) e.getSource(),
|
||||
"Select the index to insert this attribute at.",
|
||||
"Attribute Index",
|
||||
"Select the attribute to remove.",
|
||||
"Select Attribute",
|
||||
JOptionPane.PLAIN_MESSAGE,
|
||||
null,
|
||||
r.getAttributes().toArray(new Attribute[0]),
|
||||
|
|
|
@ -46,6 +46,7 @@ public class Attribute implements Serializable {
|
|||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Attribute attribute = (Attribute) o;
|
||||
return type == attribute.type &&
|
||||
relation.equals(attribute.getRelation()) &&
|
||||
name.equals(attribute.name);
|
||||
}
|
||||
|
||||
|
@ -56,6 +57,6 @@ public class Attribute implements Serializable {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getName() + ":" + this.getType().name();
|
||||
return this.getName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,5 @@ package nl.andrewlalis.erme.model;
|
|||
public enum AttributeType {
|
||||
PLAIN,
|
||||
ID_KEY,
|
||||
PARTIAL_ID_KEY,
|
||||
FOREIGN_KEY
|
||||
PARTIAL_ID_KEY
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@ import lombok.Getter;
|
|||
public class ForeignKeyAttribute extends Attribute {
|
||||
private Attribute reference;
|
||||
|
||||
public ForeignKeyAttribute(Relation relation, String name, Attribute reference) {
|
||||
super(relation, AttributeType.FOREIGN_KEY, name);
|
||||
public ForeignKeyAttribute(Relation relation, AttributeType type, String name, Attribute reference) {
|
||||
super(relation, type, name);
|
||||
this.reference = reference;
|
||||
}
|
||||
|
||||
public ForeignKeyAttribute(Relation relation, String name, String referencedRelationName, String referencedAttributeName) {
|
||||
this(relation, name, relation.getModel().findAttribute(referencedRelationName, referencedAttributeName));
|
||||
public ForeignKeyAttribute(Relation relation, AttributeType type, String name, String referencedRelationName, String referencedAttributeName) {
|
||||
this(relation, type, name, relation.getModel().findAttribute(referencedRelationName, referencedAttributeName));
|
||||
if (this.getReference() == null) {
|
||||
throw new IllegalArgumentException("Unknown attribute name.");
|
||||
}
|
||||
|
@ -22,4 +22,13 @@ public class ForeignKeyAttribute extends Attribute {
|
|||
this.reference = reference;
|
||||
this.getRelation().getModel().fireChangedEvent();
|
||||
}
|
||||
|
||||
public String getFullReferenceName() {
|
||||
return this.getReference().getRelation().getName() + "." + this.getReference().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + "->" + this.getFullReferenceName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,21 @@ public class MappingModel implements Serializable {
|
|||
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) {
|
||||
if (this.changeListeners == null) {
|
||||
this.changeListeners = new HashSet<>();
|
||||
|
|
|
@ -54,6 +54,7 @@ public class Relation implements Serializable {
|
|||
|
||||
public void removeAttribute(Attribute attribute) {
|
||||
if (this.attributes.remove(attribute)) {
|
||||
this.model.removeAllReferencingAttributes(attribute);
|
||||
this.model.fireChangedEvent();
|
||||
}
|
||||
}
|
||||
|
@ -83,4 +84,9 @@ public class Relation implements Serializable {
|
|||
public int hashCode() {
|
||||
return this.getName().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package nl.andrewlalis.erme.view;
|
|||
import lombok.Getter;
|
||||
import nl.andrewlalis.erme.control.actions.ExportToImageAction;
|
||||
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.edits.AddAttributeAction;
|
||||
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.
|
||||
*/
|
||||
private void updateActionModels() {
|
||||
NewModelAction.getInstance().setDiagramPanel(this);
|
||||
SaveAction.getInstance().setModel(this.model);
|
||||
LoadAction.getInstance().setDiagramPanel(this);
|
||||
ExportToImageAction.getInstance().setModel(this.model);
|
||||
|
|
|
@ -16,7 +16,6 @@ public class DiagramPopupMenu extends JPopupMenu {
|
|||
List<Relation> selectedRelations = model.getSelectedRelations();
|
||||
if (selectedRelations.size() == 0) {
|
||||
this.add(AddRelationAction.getInstance());
|
||||
this.add(ExportToImageAction.getInstance());
|
||||
}
|
||||
if (selectedRelations.size() > 0) {
|
||||
this.add(RemoveRelationAction.getInstance());
|
||||
|
|
|
@ -21,7 +21,7 @@ public class EditorFrame extends JFrame {
|
|||
model.addRelation(usersRelation);
|
||||
Relation tokensRelation = new Relation(model, new Point(50, 120), "Tokens");
|
||||
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"));
|
||||
model.addRelation(tokensRelation);
|
||||
|
||||
|
|
|
@ -15,12 +15,15 @@ public class EditorMenuBar extends JMenuBar {
|
|||
public EditorMenuBar() {
|
||||
this.add(this.buildFileMenu());
|
||||
this.add(this.buildEditMenu());
|
||||
this.add(this.buildHelpMenu());
|
||||
}
|
||||
|
||||
private JMenu buildFileMenu() {
|
||||
JMenu menu = new JMenu("File");
|
||||
menu.add(NewModelAction.getInstance());
|
||||
menu.add(SaveAction.getInstance());
|
||||
menu.add(LoadAction.getInstance());
|
||||
menu.addSeparator();
|
||||
menu.add(ExportToImageAction.getInstance());
|
||||
menu.addSeparator();
|
||||
menu.add(ExitAction.getInstance());
|
||||
|
@ -38,4 +41,10 @@ public class EditorMenuBar extends JMenuBar {
|
|||
menu.add(RedoAction.getInstance());
|
||||
return menu;
|
||||
}
|
||||
|
||||
private JMenu buildHelpMenu() {
|
||||
JMenu menu = new JMenu("Help");
|
||||
menu.add("About");
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package nl.andrewlalis.erme.view.view_models;
|
|||
|
||||
import nl.andrewlalis.erme.model.Attribute;
|
||||
import nl.andrewlalis.erme.model.AttributeType;
|
||||
import nl.andrewlalis.erme.model.ForeignKeyAttribute;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.font.TextAttribute;
|
||||
|
@ -13,6 +14,7 @@ public class AttributeViewModel implements ViewModel {
|
|||
public static final int PADDING_Y = 5;
|
||||
public static final Color BACKGROUND_COLOR = Color.LIGHT_GRAY;
|
||||
public static final Color FONT_COLOR = Color.BLACK;
|
||||
public static final float FK_FONT_SIZE = 11.0f;
|
||||
|
||||
private final Attribute attribute;
|
||||
|
||||
|
@ -23,33 +25,45 @@ public class AttributeViewModel implements ViewModel {
|
|||
@Override
|
||||
public void draw(Graphics2D g) {
|
||||
AttributedString as = this.getAttributedString(g);
|
||||
Rectangle r = this.getBounds(g, as);
|
||||
Rectangle r = this.getBoxBounds(g, as);
|
||||
g.setColor(BACKGROUND_COLOR);
|
||||
g.fillRect(r.x, r.y, r.width, r.height);
|
||||
g.setColor(FONT_COLOR);
|
||||
g.drawRect(r.x, r.y, r.width, r.height);
|
||||
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 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;
|
||||
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++;
|
||||
}
|
||||
Rectangle2D rect = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.attribute.getName().length(), g);
|
||||
return new Rectangle(
|
||||
x,
|
||||
y,
|
||||
(int) rect.getWidth() + (2 * PADDING_X),
|
||||
(int) rect.getHeight() + (2 * PADDING_Y)
|
||||
);
|
||||
Rectangle2D nameRect = g.getFontMetrics().getStringBounds(as.getIterator(), 0, this.attribute.getName().length(), g);
|
||||
int width = (int) nameRect.getWidth() + (2 * PADDING_X);
|
||||
int height = (int) nameRect.getHeight() + (2 * 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));
|
||||
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) {
|
||||
return this.getBounds(g, this.getAttributedString(g));
|
||||
public Rectangle getBoxBounds(Graphics2D g) {
|
||||
return this.getBoxBounds(g, this.getAttributedString(g));
|
||||
}
|
||||
|
||||
private AttributedString getAttributedString(Graphics2D g) {
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.text.AttributedString;
|
|||
public class RelationViewModel implements ViewModel {
|
||||
public static final int PADDING_X = 5;
|
||||
public static final int PADDING_Y = 5;
|
||||
public static final int ATTRIBUTE_SEPARATION = 10;
|
||||
|
||||
private final Relation relation;
|
||||
|
||||
|
@ -40,13 +41,13 @@ public class RelationViewModel implements ViewModel {
|
|||
int totalAttributeWidth = 0;
|
||||
int maxAttributeHeight = 0;
|
||||
for (Attribute a : this.relation.getAttributes()) {
|
||||
Rectangle attributeBounds = a.getViewModel().getBounds(g);
|
||||
Rectangle attributeBounds = a.getViewModel().getBoxBounds(g);
|
||||
totalAttributeWidth += attributeBounds.width;
|
||||
maxAttributeHeight = Math.max(maxAttributeHeight, attributeBounds.height);
|
||||
}
|
||||
Rectangle nameBounds = this.getNameBounds(g);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue