diff --git a/README.md b/README.md index bed16db..f055f68 100644 --- a/README.md +++ b/README.md @@ -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 ` from the command line (where `` 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 diff --git a/design/main_interface.PNG b/design/main_interface.PNG index bca62de..79e16ab 100644 Binary files a/design/main_interface.PNG and b/design/main_interface.PNG differ diff --git a/design/sample_export.png b/design/sample_export.png index 96e323f..360f25c 100644 Binary files a/design/sample_export.png and b/design/sample_export.png differ diff --git a/pom.xml b/pom.xml index 029aacf..74982c4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ nl.andrewlalis EntityRelationMappingEditor - 1.5.0 + 1.6.0 @@ -50,13 +50,19 @@ com.formdev flatlaf - 1.0-rc3 + 1.6.1 org.projectlombok lombok - 1.18.16 + 1.18.22 provided + + + com.fasterxml.jackson.core + jackson-databind + 2.13.0 + \ No newline at end of file diff --git a/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java b/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java index 75fcb5f..faaf929 100644 --- a/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java +++ b/src/main/java/nl/andrewlalis/erme/EntityRelationMappingEditor.java @@ -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"); - } } diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java index 9531830..c106ebb 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/ExportToImageAction.java @@ -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; diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/HtmlDocumentViewerAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/HtmlDocumentViewerAction.java index b23dcf9..640238e 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/HtmlDocumentViewerAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/HtmlDocumentViewerAction.java @@ -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; diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/LoadAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/LoadAction.java index 1e85b14..d4e18c7 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/LoadAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/LoadAction.java @@ -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); } diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/SaveAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/SaveAction.java index 2310d55..7c8d261 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/SaveAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/SaveAction.java @@ -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) { diff --git a/src/main/java/nl/andrewlalis/erme/model/Attribute.java b/src/main/java/nl/andrewlalis/erme/model/Attribute.java index 4f9bcb0..515222b 100644 --- a/src/main/java/nl/andrewlalis/erme/model/Attribute.java +++ b/src/main/java/nl/andrewlalis/erme/model/Attribute.java @@ -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; @@ -32,7 +31,7 @@ public class Attribute implements Serializable { this.name = name; this.relation.getModel().fireChangedEvent(); } - + public AttributeViewModel getViewModel() { if (this.viewModel == null) { this.viewModel = new AttributeViewModel(this); diff --git a/src/main/java/nl/andrewlalis/erme/model/MappingModel.java b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java index b7763d0..545ff92 100644 --- a/src/main/java/nl/andrewlalis/erme/model/MappingModel.java +++ b/src/main/java/nl/andrewlalis/erme/model/MappingModel.java @@ -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 relations; - private transient Set changeListeners; - - private final static long serialVersionUID = 6153776381873250304L; + private transient final Set 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 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 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 references = buildReferenceMap(model, node); + while (!references.isEmpty()) { + boolean workDone = false; + for (Map.Entry 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 buildReferenceMap(MappingModel model, ObjectNode node) { + Map 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; + } } diff --git a/src/main/java/nl/andrewlalis/erme/model/Relation.java b/src/main/java/nl/andrewlalis/erme/model/Relation.java index 8393002..4a48350 100644 --- a/src/main/java/nl/andrewlalis/erme/model/Relation.java +++ b/src/main/java/nl/andrewlalis/erme/model/Relation.java @@ -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 { +public class Relation implements Viewable, Comparable { private final MappingModel model; private Point position; private String name; @@ -24,11 +23,15 @@ public class Relation implements Serializable, Viewable, Comparable { 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 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) { diff --git a/src/main/java/nl/andrewlalis/erme/util/Hash.java b/src/main/java/nl/andrewlalis/erme/util/Hash.java deleted file mode 100644 index 2e9a25a..0000000 --- a/src/main/java/nl/andrewlalis/erme/util/Hash.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java index 5e4e8b6..7e3735a 100644 --- a/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java +++ b/src/main/java/nl/andrewlalis/erme/view/DiagramPanel.java @@ -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); diff --git a/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java b/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java index 8bf536d..1f8435d 100644 --- a/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java +++ b/src/main/java/nl/andrewlalis/erme/view/EditorFrame.java @@ -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(); diff --git a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java index afd76fa..90114c0 100644 --- a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java +++ b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java @@ -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()); diff --git a/src/main/resources/admin_hash.txt b/src/main/resources/admin_hash.txt deleted file mode 100644 index d1f06e0..0000000 --- a/src/main/resources/admin_hash.txt +++ /dev/null @@ -1 +0,0 @@ -cfdabe75d984e5a92fb491dadc9091419d9587c049246356a488e83a75505bce \ No newline at end of file diff --git a/src/main/resources/html/instructions.html b/src/main/resources/html/instructions.html index 79c3cba..b5b5bb0 100644 --- a/src/main/resources/html/instructions.html +++ b/src/main/resources/html/instructions.html @@ -12,7 +12,7 @@

A simple UI for editing entity-relation mapping diagrams.

- 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? Create a new issue on GitHub!

diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png new file mode 100644 index 0000000..27ab608 Binary files /dev/null and b/src/main/resources/icon.png differ