Removed useless admin stuff, changed to save files as JSON, added image scaling for exports.
This commit is contained in:
parent
d63eb3345f
commit
bc464ba42b
|
@ -1,6 +1,11 @@
|
|||
# Entity-Relation Mapping Editor
|
||||
A simple UI for editing entity-relation mapping diagrams.
|
||||
|
||||
## Usage
|
||||
This program is distributed as an executable **jar** file. You can find the latest release [here](https://github.com/andrewlalis/EntityRelationMappingEditor/releases). You will need Java installed on your computer (version 8 or higher). [You can install an OpenJDK version of Java here.](https://adoptium.net/)
|
||||
|
||||
Simply double-click on the jar file, or execute `java -jar <jar file>` from the command line (where `<jar file>` is replaced with the path to your actual jar file).
|
||||
|
||||
![window screenshot](https://raw.githubusercontent.com/andrewlalis/EntityRelationMappingEditor/main/design/main_interface.PNG)
|
||||
|
||||
## How to Use
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 14 KiB |
12
pom.xml
12
pom.xml
|
@ -6,7 +6,7 @@
|
|||
|
||||
<groupId>nl.andrewlalis</groupId>
|
||||
<artifactId>EntityRelationMappingEditor</artifactId>
|
||||
<version>1.5.0</version>
|
||||
<version>1.6.0</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
@ -50,13 +50,19 @@
|
|||
<dependency>
|
||||
<groupId>com.formdev</groupId>
|
||||
<artifactId>flatlaf</artifactId>
|
||||
<version>1.0-rc3</version>
|
||||
<version>1.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.16</version>
|
||||
<version>1.18.22</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.13.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,31 +1,16 @@
|
|||
package nl.andrewlalis.erme;
|
||||
|
||||
import com.formdev.flatlaf.FlatLightLaf;
|
||||
import nl.andrewlalis.erme.util.Hash;
|
||||
import nl.andrewlalis.erme.view.EditorFrame;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class EntityRelationMappingEditor {
|
||||
public static final String VERSION = "1.5.0";
|
||||
public static final String VERSION = "1.6.0";
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (!FlatLightLaf.install()) {
|
||||
if (!FlatLightLaf.setup()) {
|
||||
System.err.println("Could not install FlatLight Look and Feel.");
|
||||
}
|
||||
final boolean includeAdminActions = shouldIncludeAdminActions(args);
|
||||
if (includeAdminActions) {
|
||||
System.out.println("Admin actions have been enabled.");
|
||||
}
|
||||
EditorFrame frame = new EditorFrame(includeAdminActions);
|
||||
EditorFrame frame = new EditorFrame();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
private static boolean shouldIncludeAdminActions(String[] args) {
|
||||
if (args.length < 1) {
|
||||
return false;
|
||||
}
|
||||
byte[] pw = args[0].getBytes(StandardCharsets.UTF_8);
|
||||
return Hash.matches(pw, "admin_hash.txt");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import lombok.Setter;
|
|||
import nl.andrewlalis.erme.model.MappingModel;
|
||||
import nl.andrewlalis.erme.model.Relation;
|
||||
import nl.andrewlalis.erme.view.DiagramPanel;
|
||||
import nl.andrewlalis.erme.view.view_models.AttributeViewModel;
|
||||
import nl.andrewlalis.erme.view.view_models.MappingModelViewModel;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
@ -77,9 +76,18 @@ public class ExportToImageAction extends AbstractAction {
|
|||
} else {
|
||||
chosenFile = new File(chosenFile.getParent(), chosenFile.getName() + '.' + extension);
|
||||
}
|
||||
String input = JOptionPane.showInputDialog(this.diagramPanel, "Choose a scale for the image.", "3.0");
|
||||
float scale;
|
||||
try {
|
||||
scale = Float.parseFloat(input);
|
||||
if (scale <= 0.0f || scale > 64.0f) throw new IllegalArgumentException();
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(this.diagramPanel, "Invalid scale value. Should be a positive number less than 64.", "Invalid Scale", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
BufferedImage render = this.renderModel();
|
||||
BufferedImage render = this.renderModel(scale);
|
||||
double durationSeconds = (System.currentTimeMillis() - start) / 1000.0;
|
||||
ImageIO.write(render, extension, chosenFile);
|
||||
prefs.put(LAST_EXPORT_LOCATION_KEY, chosenFile.getAbsolutePath());
|
||||
|
@ -97,12 +105,19 @@ public class ExportToImageAction extends AbstractAction {
|
|||
}
|
||||
}
|
||||
|
||||
private BufferedImage renderModel() {
|
||||
/**
|
||||
* Renders the mapping model to an image with the given resolution.
|
||||
* @param scale The scale to use. Should be greater than zero.
|
||||
* @return The image which was rendered.
|
||||
*/
|
||||
private BufferedImage renderModel(float scale) {
|
||||
// Prepare a tiny sample image that we can use to determine the bounds of the model in a graphics context.
|
||||
BufferedImage bufferedImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = bufferedImage.createGraphics();
|
||||
DiagramPanel.prepareGraphics(g2d);
|
||||
final Rectangle bounds = this.model.getViewModel().getBounds(g2d);
|
||||
bounds.width *= scale;
|
||||
bounds.height *= scale;
|
||||
|
||||
// Prepare the output image.
|
||||
BufferedImage outputImage = new BufferedImage(bounds.width, bounds.height + 20, BufferedImage.TYPE_INT_RGB);
|
||||
|
@ -112,7 +127,10 @@ public class ExportToImageAction extends AbstractAction {
|
|||
|
||||
// Transform the graphics space to account for the model's offset from origin.
|
||||
AffineTransform originalTransform = g2d.getTransform();
|
||||
g2d.setTransform(AffineTransform.getTranslateInstance(-bounds.x, -bounds.y));
|
||||
AffineTransform modelTransform = new AffineTransform();
|
||||
modelTransform.scale(scale, scale);
|
||||
modelTransform.translate(-bounds.x, -bounds.y);
|
||||
g2d.setTransform(modelTransform);
|
||||
DiagramPanel.prepareGraphics(g2d);
|
||||
|
||||
// Render the model.
|
||||
|
@ -124,9 +142,9 @@ public class ExportToImageAction extends AbstractAction {
|
|||
this.model.getRelations().forEach(r -> r.setSelected(selectedRelations.contains(r)));
|
||||
LolcatAction.getInstance().setLolcatEnabled(lolcat); // revert previous lolcat mode
|
||||
|
||||
// Revert back to the normal image space, and render a watermark.
|
||||
// Revert to the normal image space, and render a watermark.
|
||||
g2d.setTransform(originalTransform);
|
||||
g2d.setColor(Color.LIGHT_GRAY);
|
||||
g2d.setColor(Color.decode("#e8e8e8"));
|
||||
g2d.setFont(g2d.getFont().deriveFont(10.0f));
|
||||
g2d.drawString("Created by EntityRelationMappingEditor", 0, outputImage.getHeight() - 3);
|
||||
return outputImage;
|
||||
|
|
|
@ -13,6 +13,9 @@ import java.io.InputStream;
|
|||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/**
|
||||
* An action which, when performed, opens a view that displays an HTML document.
|
||||
*/
|
||||
public abstract class HtmlDocumentViewerAction extends AbstractAction {
|
||||
private final String resourceFileName;
|
||||
private final Dialog.ModalityType modalityType;
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
package nl.andrewlalis.erme.control.actions;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.Setter;
|
||||
import nl.andrewlalis.erme.model.MappingModel;
|
||||
import nl.andrewlalis.erme.view.DiagramPanel;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
public class LoadAction extends AbstractAction {
|
||||
|
@ -40,8 +43,8 @@ public class LoadAction extends AbstractAction {
|
|||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
FileNameExtensionFilter filter = new FileNameExtensionFilter(
|
||||
"ERME Serialized Files",
|
||||
"erme"
|
||||
"JSON Files",
|
||||
"json"
|
||||
);
|
||||
fileChooser.setFileFilter(filter);
|
||||
Preferences prefs = Preferences.userNodeForPackage(LoadAction.class);
|
||||
|
@ -56,11 +59,15 @@ public class LoadAction extends AbstractAction {
|
|||
JOptionPane.showMessageDialog(fileChooser, "The selected file cannot be read.", "Invalid File", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(chosenFile))) {
|
||||
MappingModel loadedModel = (MappingModel) ois.readObject();
|
||||
this.diagramPanel.setModel(loadedModel);
|
||||
try (FileInputStream fis = new FileInputStream(chosenFile)) {
|
||||
ObjectMapper mapper = JsonMapper.builder()
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true)
|
||||
.build();
|
||||
JsonNode data = mapper.readValue(fis, JsonNode.class);
|
||||
this.diagramPanel.setModel(MappingModel.fromJson((ObjectNode) data));
|
||||
prefs.put(LAST_LOAD_LOCATION_KEY, chosenFile.getAbsolutePath());
|
||||
} catch (IOException | ClassNotFoundException | ClassCastException ex) {
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
JOptionPane.showMessageDialog(fileChooser, "An error occurred and the file could not be read:\n" + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
package nl.andrewlalis.erme.control.actions;
|
||||
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import lombok.Setter;
|
||||
import nl.andrewlalis.erme.model.MappingModel;
|
||||
import nl.andrewlalis.erme.view.DiagramPanel;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
public class SaveAction extends AbstractAction {
|
||||
|
@ -29,6 +31,7 @@ public class SaveAction extends AbstractAction {
|
|||
|
||||
@Setter
|
||||
private MappingModel model;
|
||||
|
||||
@Setter
|
||||
private DiagramPanel diagramPanel;
|
||||
|
||||
|
@ -42,8 +45,8 @@ public class SaveAction extends AbstractAction {
|
|||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
FileNameExtensionFilter filter = new FileNameExtensionFilter(
|
||||
"ERME Serialized Files",
|
||||
"erme"
|
||||
"JSON Files",
|
||||
"json"
|
||||
);
|
||||
fileChooser.setFileFilter(filter);
|
||||
Preferences prefs = Preferences.userNodeForPackage(SaveAction.class);
|
||||
|
@ -55,15 +58,23 @@ public class SaveAction extends AbstractAction {
|
|||
if (choice == JFileChooser.APPROVE_OPTION) {
|
||||
File chosenFile = fileChooser.getSelectedFile();
|
||||
if (chosenFile == null || chosenFile.isDirectory()) {
|
||||
JOptionPane.showMessageDialog(fileChooser, "The selected file cannot be written to.", "Invalid File", JOptionPane.WARNING_MESSAGE);
|
||||
JOptionPane.showMessageDialog(this.diagramPanel, "The selected file cannot be written to.", "Invalid File", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (!chosenFile.exists() && !chosenFile.getName().endsWith(".erme")) {
|
||||
chosenFile = new File(chosenFile.getParent(), chosenFile.getName() + ".erme");
|
||||
if (!chosenFile.exists() && !chosenFile.getName().endsWith(".json")) {
|
||||
chosenFile = new File(chosenFile.getParent(), chosenFile.getName() + ".json");
|
||||
} else if (chosenFile.exists()) {
|
||||
int result = JOptionPane.showConfirmDialog(this.diagramPanel, "Are you sure you want overwrite this file?", "Overwrite", JOptionPane.YES_NO_OPTION);
|
||||
if (result == JOptionPane.NO_OPTION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TODO: Check for confirm before overwriting.
|
||||
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(chosenFile))) {
|
||||
oos.writeObject(this.model);
|
||||
try (FileOutputStream fos = new FileOutputStream(chosenFile)) {
|
||||
ObjectMapper mapper = JsonMapper.builder()
|
||||
.configure(SerializationFeature.INDENT_OUTPUT, true)
|
||||
.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true)
|
||||
.build();
|
||||
mapper.writeValue(fos, this.model.toJson(mapper));
|
||||
prefs.put(LAST_SAVE_LOCATION_KEY, chosenFile.getAbsolutePath());
|
||||
JOptionPane.showMessageDialog(fileChooser, "File saved successfully.", "Success", JOptionPane.INFORMATION_MESSAGE);
|
||||
} catch (IOException ex) {
|
||||
|
|
|
@ -3,14 +3,13 @@ package nl.andrewlalis.erme.model;
|
|||
import lombok.Getter;
|
||||
import nl.andrewlalis.erme.view.view_models.AttributeViewModel;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A single value that belongs to a relation.
|
||||
*/
|
||||
@Getter
|
||||
public class Attribute implements Serializable {
|
||||
public class Attribute {
|
||||
private final Relation relation;
|
||||
private AttributeType type;
|
||||
private String name;
|
||||
|
|
|
@ -1,29 +1,27 @@
|
|||
package nl.andrewlalis.erme.model;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.Getter;
|
||||
import nl.andrewlalis.erme.view.OrderableListPanel;
|
||||
import nl.andrewlalis.erme.view.view_models.MappingModelViewModel;
|
||||
import nl.andrewlalis.erme.view.view_models.ViewModel;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This model contains all the information about a single mapping diagram,
|
||||
* including each mapped table and the links between them.
|
||||
*/
|
||||
public class MappingModel implements Serializable, Viewable {
|
||||
public class MappingModel implements Viewable {
|
||||
@Getter
|
||||
private final Set<Relation> relations;
|
||||
|
||||
private transient Set<ModelChangeListener> changeListeners;
|
||||
|
||||
private final static long serialVersionUID = 6153776381873250304L;
|
||||
private transient final Set<ModelChangeListener> changeListeners;
|
||||
|
||||
public MappingModel() {
|
||||
this.relations = new HashSet<>();
|
||||
|
@ -42,10 +40,20 @@ public class MappingModel implements Serializable, Viewable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of relations which are currently selected.
|
||||
* @return The list of relations which are selected.
|
||||
*/
|
||||
public List<Relation> getSelectedRelations() {
|
||||
return this.relations.stream().filter(Relation::isSelected).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an attribute in this model, or returns null otherwise.
|
||||
* @param relationName The name of the relation the attribute is in.
|
||||
* @param attributeName The name of the attribute.
|
||||
* @return The attribute which was found, or null if none was found.
|
||||
*/
|
||||
public Attribute findAttribute(String relationName, String attributeName) {
|
||||
for (Relation r : this.getRelations()) {
|
||||
if (!r.getName().equals(relationName)) continue;
|
||||
|
@ -58,6 +66,11 @@ public class MappingModel implements Serializable, Viewable {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all attributes from any relation in the model which reference the
|
||||
* given attribute.
|
||||
* @param referenced The attribute to remove references from.
|
||||
*/
|
||||
public void removeAllReferencingAttributes(Attribute referenced) {
|
||||
for (Relation r : this.getRelations()) {
|
||||
Set<Attribute> removalSet = new HashSet<>();
|
||||
|
@ -73,6 +86,10 @@ public class MappingModel implements Serializable, Viewable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bounding rectangle around all relations of the model.
|
||||
* @return The bounding rectangle around all relations in this model.
|
||||
*/
|
||||
public Rectangle getRelationBounds() {
|
||||
if (this.getRelations().isEmpty()) {
|
||||
return new Rectangle(0, 0, 0, 0);
|
||||
|
@ -90,14 +107,20 @@ public class MappingModel implements Serializable, Viewable {
|
|||
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to this model, which will be notified of changes to the
|
||||
* model.
|
||||
* @param listener The listener to add.
|
||||
*/
|
||||
public void addChangeListener(ModelChangeListener listener) {
|
||||
if (this.changeListeners == null) {
|
||||
this.changeListeners = new HashSet<>();
|
||||
}
|
||||
this.changeListeners.add(listener);
|
||||
listener.onModelChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires an all-purpose event which notifies all listeners that the model
|
||||
* has changed.
|
||||
*/
|
||||
public final void fireChangedEvent() {
|
||||
this.changeListeners.forEach(ModelChangeListener::onModelChanged);
|
||||
}
|
||||
|
@ -146,4 +169,106 @@ public class MappingModel implements Serializable, Viewable {
|
|||
this.getRelations().forEach(r -> c.addRelation(r.copy(c)));
|
||||
return c;
|
||||
}
|
||||
|
||||
public ObjectNode toJson(ObjectMapper mapper) {
|
||||
ObjectNode node = mapper.createObjectNode();
|
||||
ArrayNode relationsArray = node.withArray("relations");
|
||||
for (Relation r : this.relations) {
|
||||
ObjectNode relationNode = mapper.createObjectNode()
|
||||
.put("name", r.getName());
|
||||
ObjectNode positionNode = mapper.createObjectNode()
|
||||
.put("x", r.getPosition().x)
|
||||
.put("y", r.getPosition().y);
|
||||
relationNode.set("position", positionNode);
|
||||
ArrayNode attributesArray = relationNode.withArray("attributes");
|
||||
for (Attribute a : r.getAttributes()) {
|
||||
ObjectNode attributeNode = mapper.createObjectNode()
|
||||
.put("name", a.getName())
|
||||
.put("type", a.getType().name());
|
||||
if (a instanceof ForeignKeyAttribute) {
|
||||
ForeignKeyAttribute fk = (ForeignKeyAttribute) a;
|
||||
ObjectNode referenceNode = mapper.createObjectNode()
|
||||
.put("relation", fk.getReference().getRelation().getName())
|
||||
.put("attribute", fk.getReference().getName());
|
||||
attributeNode.set("references", referenceNode);
|
||||
}
|
||||
attributesArray.add(attributeNode);
|
||||
}
|
||||
relationsArray.add(relationNode);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
public static MappingModel fromJson(ObjectNode node) {
|
||||
MappingModel model = new MappingModel();
|
||||
for (JsonNode relationNodeRaw : node.withArray("relations")) {
|
||||
if (!relationNodeRaw.isObject()) throw new IllegalArgumentException();
|
||||
ObjectNode relationNode = (ObjectNode) relationNodeRaw;
|
||||
String name = relationNode.get("name").asText();
|
||||
int x = relationNode.get("position").get("x").asInt();
|
||||
int y = relationNode.get("position").get("y").asInt();
|
||||
Point position = new Point(x, y);
|
||||
Relation relation = new Relation(model, position, name);
|
||||
for (JsonNode attributeNodeRaw : relationNode.withArray("attributes")) {
|
||||
if (!attributeNodeRaw.isObject()) throw new IllegalArgumentException();
|
||||
ObjectNode attributeNode = (ObjectNode) attributeNodeRaw;
|
||||
String attributeName = attributeNode.get("name").asText();
|
||||
AttributeType type = AttributeType.valueOf(attributeNode.get("type").asText().toUpperCase());
|
||||
Attribute attribute = new Attribute(relation, type, attributeName);
|
||||
relation.addAttribute(attribute);
|
||||
}
|
||||
model.addRelation(relation);
|
||||
}
|
||||
addForeignKeys(model, node);
|
||||
return model;
|
||||
}
|
||||
|
||||
private static void addForeignKeys(MappingModel model, ObjectNode node) {
|
||||
Map<Attribute, ObjectNode> references = buildReferenceMap(model, node);
|
||||
while (!references.isEmpty()) {
|
||||
boolean workDone = false;
|
||||
for (Map.Entry<Attribute, ObjectNode> entry : references.entrySet()) {
|
||||
Attribute attribute = entry.getKey();
|
||||
String referencedName = entry.getValue().get("attribute").asText();
|
||||
String referencedRelation = entry.getValue().get("relation").asText();
|
||||
Attribute referencedAttribute = model.findAttribute(referencedRelation, referencedName);
|
||||
if (referencedAttribute == null) throw new IllegalArgumentException("Foreign key referenced unknown attribute.");
|
||||
if (!references.containsKey(referencedAttribute)) {
|
||||
ForeignKeyAttribute fk = new ForeignKeyAttribute(attribute.getRelation(), attribute.getType(), attribute.getName(), referencedAttribute);
|
||||
attribute.getRelation().removeAttribute(attribute);
|
||||
attribute.getRelation().addAttribute(fk);
|
||||
references.remove(attribute);
|
||||
workDone = true;
|
||||
}
|
||||
}
|
||||
if (!workDone) {
|
||||
throw new IllegalArgumentException("Invalid foreign key structure. Possible cyclic references.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a map that contains the set of foreign key references, indexed by
|
||||
* the primitive attribute that is referencing another.
|
||||
* @param model The model to lookup attributes from.
|
||||
* @param node The raw JSON data for the model.
|
||||
* @return A map containing foreign key references, to be used to build a
|
||||
* complete model with foreign key attributes.
|
||||
*/
|
||||
private static Map<Attribute, ObjectNode> buildReferenceMap(MappingModel model, ObjectNode node) {
|
||||
Map<Attribute, ObjectNode> references = new HashMap<>();
|
||||
for (JsonNode r : node.withArray("relations")) {
|
||||
for (JsonNode a : r.withArray("attributes")) {
|
||||
if (a.has("references") && a.get("references").isObject()) {
|
||||
ObjectNode referenceNode = (ObjectNode) a.get("references");
|
||||
String attributeName = a.get("name").asText();
|
||||
String relationName = r.get("name").asText();
|
||||
Attribute attribute = model.findAttribute(relationName, attributeName);
|
||||
if (attribute == null) throw new IllegalArgumentException("Mapping model is not complete. Missing attribute " + attributeName + " in relation " + relationName + ".");
|
||||
references.put(attribute, referenceNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return references;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import nl.andrewlalis.erme.view.view_models.RelationViewModel;
|
|||
import nl.andrewlalis.erme.view.view_models.ViewModel;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
@ -15,7 +14,7 @@ import java.util.stream.Collectors;
|
|||
* Represents a single "relation" or table in the diagram.
|
||||
*/
|
||||
@Getter
|
||||
public class Relation implements Serializable, Viewable, Comparable<Relation> {
|
||||
public class Relation implements Viewable, Comparable<Relation> {
|
||||
private final MappingModel model;
|
||||
private Point position;
|
||||
private String name;
|
||||
|
@ -24,11 +23,15 @@ public class Relation implements Serializable, Viewable, Comparable<Relation> {
|
|||
private transient boolean selected;
|
||||
private transient RelationViewModel viewModel;
|
||||
|
||||
public Relation(MappingModel model, Point position, String name) {
|
||||
public Relation(MappingModel model, Point position, String name, List<Attribute> attributes) {
|
||||
this.model = model;
|
||||
this.position = position;
|
||||
this.name = name;
|
||||
this.attributes = new ArrayList<>();
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public Relation(MappingModel model, Point position, String name) {
|
||||
this(model, position, name, new ArrayList<>());
|
||||
}
|
||||
|
||||
public void setPosition(Point position) {
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package nl.andrewlalis.erme.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Hash {
|
||||
public static boolean matches(byte[] password, String resourceFile) {
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
byte[] passwordHash = md.digest(password);
|
||||
InputStream is = Hash.class.getClassLoader().getResourceAsStream(resourceFile);
|
||||
if (is == null) {
|
||||
System.err.println("Could not obtain input stream to admin_hash.txt");
|
||||
return false;
|
||||
}
|
||||
char[] buffer = new char[64];
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
|
||||
if (br.read(buffer) != buffer.length) {
|
||||
System.err.println("Incorrect number of characters read from hash file.");
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
String hashHex = String.valueOf(buffer);
|
||||
byte[] hash = hexStringToByteArray(hashHex);
|
||||
return Arrays.equals(passwordHash, hash);
|
||||
}
|
||||
|
||||
private static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -117,6 +117,7 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
|
|||
|
||||
/**
|
||||
* Updates all the action singletons with the latest model information.
|
||||
* TODO: Clean this up somehow!
|
||||
*/
|
||||
private void updateActionModels() {
|
||||
NewModelAction.getInstance().setDiagramPanel(this);
|
||||
|
@ -134,10 +135,10 @@ public class DiagramPanel extends JPanel implements ModelChangeListener {
|
|||
RemoveAttributeAction.getInstance().setDiagramPanel(this);
|
||||
LoadSampleModelAction.getInstance().setDiagramPanel(this);
|
||||
LolcatAction.getInstance().setDiagramPanel(this);
|
||||
AutoPositionAction.getInstance().setDiagramPanel(this);
|
||||
AutoPositionAction.getInstance().setDiagramPanel(this);
|
||||
AutoPositionAction.getInstance().setModel(this.model);
|
||||
OrderableListPanel.getInstance().setModel(this.model);
|
||||
AboutAction.getInstance().setDiagramPanel(this);
|
||||
AboutAction.getInstance().setDiagramPanel(this);
|
||||
ExitAction.getInstance().setDiagramPanel(this);
|
||||
InstructionsAction.getInstance().setDiagramPanel(this);
|
||||
MappingAlgorithmHelpAction.getInstance().setDiagramPanel(this);
|
||||
|
|
|
@ -2,17 +2,30 @@ package nl.andrewlalis.erme.view;
|
|||
|
||||
import nl.andrewlalis.erme.model.MappingModel;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* The main JFrame for the editor.
|
||||
*/
|
||||
public class EditorFrame extends JFrame {
|
||||
public EditorFrame(boolean includeAdminActions) {
|
||||
public EditorFrame() {
|
||||
super("ER-Mapping Editor");
|
||||
this.setContentPane(new DiagramPanel(new MappingModel()));
|
||||
this.setJMenuBar(new EditorMenuBar(includeAdminActions));
|
||||
this.setJMenuBar(new EditorMenuBar());
|
||||
try {
|
||||
InputStream is = getClass().getClassLoader().getResourceAsStream("icon.png");
|
||||
if (is == null) {
|
||||
System.err.println("Could not load application icon.");
|
||||
} else {
|
||||
this.setIconImage(ImageIO.read(is));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.setMinimumSize(new Dimension(400, 400));
|
||||
this.setPreferredSize(new Dimension(800, 800));
|
||||
this.pack();
|
||||
|
|
|
@ -12,10 +12,7 @@ import javax.swing.*;
|
|||
* The menu bar that's visible atop the application.
|
||||
*/
|
||||
public class EditorMenuBar extends JMenuBar {
|
||||
private final boolean includeAdminActions;
|
||||
|
||||
public EditorMenuBar(boolean includeAdminActions) {
|
||||
this.includeAdminActions = includeAdminActions;
|
||||
public EditorMenuBar() {
|
||||
this.add(this.buildFileMenu());
|
||||
this.add(this.buildEditMenu());
|
||||
this.add(this.buildHelpMenu());
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
cfdabe75d984e5a92fb491dadc9091419d9587c049246356a488e83a75505bce
|
|
@ -12,7 +12,7 @@
|
|||
<p><em>A simple UI for editing entity-relation mapping diagrams.</em></p>
|
||||
|
||||
<p>
|
||||
Have you noticed any unexpected behavior? Is there something you thing would make a good addition to this application?
|
||||
Have you noticed any unexpected behavior? Is there something you think would make a good addition to this application?
|
||||
<a href="https://github.com/andrewlalis/EntityRelationMappingEditor/issues">Create a new issue on GitHub!</a>
|
||||
</p>
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
Loading…
Reference in New Issue