diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/HtmlDocumentViewerAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/HtmlDocumentViewerAction.java new file mode 100644 index 0000000..22f3348 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/control/actions/HtmlDocumentViewerAction.java @@ -0,0 +1,84 @@ +package nl.andrewlalis.erme.control.actions; + +import javax.swing.*; +import javax.swing.event.HyperlinkEvent; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URISyntaxException; + +public abstract class HtmlDocumentViewerAction extends AbstractAction { + private final String resourceFileName; + + public HtmlDocumentViewerAction(String name, String resourceFileName) { + super(name); + this.resourceFileName = resourceFileName; + } + + @Override + public void actionPerformed(ActionEvent e) { + JDialog dialog = new JDialog( + SwingUtilities.getWindowAncestor((Component) e.getSource()), + (String) this.getValue(NAME), + Dialog.ModalityType.APPLICATION_MODAL + ); + JTextPane textPane = new JTextPane(); + textPane.setEditable(false); + textPane.setContentType("text/html"); + try { + textPane.setText(this.readFile()); + textPane.addHyperlinkListener(event -> { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + if (!Desktop.isDesktopSupported()) { + JOptionPane.showMessageDialog(dialog, "Desktop API not supported. You may still visit the link manually:\n" + event.getURL(), "Desktop API Not Supported", JOptionPane.WARNING_MESSAGE); + } else { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.browse(event.getURL().toURI()); + } catch (IOException | URISyntaxException ex) { + ex.printStackTrace(); + JOptionPane.showMessageDialog(dialog, "An error occurred and the URL could not be opened:\n" + event.getURL(), "URL Could Not Open", JOptionPane.ERROR_MESSAGE); + } + } + } + }); + } catch (IOException ex) { + ex.printStackTrace(); + JOptionPane.showMessageDialog( + (Component) e.getSource(), + "An error occured:\n" + ex.getMessage(), + "Error", + JOptionPane.ERROR_MESSAGE + ); + textPane.setContentType("text/plain"); + textPane.setText("Unable to load content."); + } + textPane.setCaretPosition(0); + JScrollPane scrollPane = new JScrollPane(textPane, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + dialog.setContentPane(scrollPane); + dialog.setMaximumSize(new Dimension(600, 800)); + dialog.setPreferredSize(new Dimension(600, 800)); + dialog.pack(); + dialog.setLocationRelativeTo(null); + dialog.setVisible(true); + } + + private String readFile() throws IOException { + InputStream is = getClass().getClassLoader().getResourceAsStream(this.resourceFileName); + if (is == null) { + throw new IOException("Could not get stream for " + this.resourceFileName); + } + StringBuilder sb = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { + String line = reader.readLine(); + while (line != null) { + sb.append(line).append('\n'); + line = reader.readLine(); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/InstructionsAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/InstructionsAction.java index 1233cad..258df91 100644 --- a/src/main/java/nl/andrewlalis/erme/control/actions/InstructionsAction.java +++ b/src/main/java/nl/andrewlalis/erme/control/actions/InstructionsAction.java @@ -1,15 +1,6 @@ package nl.andrewlalis.erme.control.actions; -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.io.*; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Files; - -public class InstructionsAction extends AbstractAction { +public class InstructionsAction extends HtmlDocumentViewerAction { private static InstructionsAction instance; public static InstructionsAction getInstance() { @@ -18,66 +9,7 @@ public class InstructionsAction extends AbstractAction { } public InstructionsAction() { - super("Instructions"); + super("Instructions", "html/instructions.html"); this.putValue(SHORT_DESCRIPTION, "Instructions for how to use this program."); } - - @Override - public void actionPerformed(ActionEvent e) { - JDialog dialog = new JDialog(SwingUtilities.getWindowAncestor((Component) e.getSource()), "Instructions", Dialog.ModalityType.APPLICATION_MODAL); - JTextPane textPane = new JTextPane(); - textPane.setEditable(false); - textPane.setContentType("text/html"); - try { - textPane.setText(this.readFile()); - textPane.addHyperlinkListener(event -> { - if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - if (!Desktop.isDesktopSupported()) { - JOptionPane.showMessageDialog(dialog, "Desktop API not supported. You may still visit the link manually:\n" + event.getURL(), "Desktop API Not Supported", JOptionPane.WARNING_MESSAGE); - } else { - Desktop desktop = Desktop.getDesktop(); - try { - desktop.browse(event.getURL().toURI()); - } catch (IOException | URISyntaxException ex) { - ex.printStackTrace(); - JOptionPane.showMessageDialog(dialog, "An error occurred and the URL could not be opened:\n" + event.getURL(), "URL Could Not Open", JOptionPane.ERROR_MESSAGE); - } - } - } - }); - } catch (IOException ex) { - ex.printStackTrace(); - JOptionPane.showMessageDialog( - (Component) e.getSource(), - "An error occured:\n" + ex.getMessage(), - "Error", - JOptionPane.ERROR_MESSAGE - ); - textPane.setContentType("text/plain"); - textPane.setText("Unable to load content."); - } - JScrollPane scrollPane = new JScrollPane(textPane, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - dialog.setContentPane(scrollPane); - dialog.setMaximumSize(new Dimension(600, 800)); - dialog.setPreferredSize(new Dimension(600, 800)); - dialog.pack(); - dialog.setLocationRelativeTo(null); - dialog.setVisible(true); - } - - private String readFile() throws IOException { - InputStream is = getClass().getClassLoader().getResourceAsStream("html/instructions.html"); - if (is == null) { - throw new IOException("Could not get stream for instructions.html."); - } - StringBuilder sb = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { - String line = reader.readLine(); - while (line != null) { - sb.append(line).append('\n'); - line = reader.readLine(); - } - } - return sb.toString(); - } } diff --git a/src/main/java/nl/andrewlalis/erme/control/actions/MappingAlgorithmHelpAction.java b/src/main/java/nl/andrewlalis/erme/control/actions/MappingAlgorithmHelpAction.java new file mode 100644 index 0000000..dea1d87 --- /dev/null +++ b/src/main/java/nl/andrewlalis/erme/control/actions/MappingAlgorithmHelpAction.java @@ -0,0 +1,17 @@ +package nl.andrewlalis.erme.control.actions; + +public class MappingAlgorithmHelpAction extends HtmlDocumentViewerAction { + private static MappingAlgorithmHelpAction instance; + + public static MappingAlgorithmHelpAction getInstance() { + if (instance == null) { + instance = new MappingAlgorithmHelpAction(); + } + return instance; + } + + public MappingAlgorithmHelpAction() { + super("Mapping Algorithm Help", "html/er_mapping_algorithm.html"); + this.putValue(SHORT_DESCRIPTION, "Shows a quick guide on how to map from an ER model to a schema."); + } +} diff --git a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java index 76e2e36..3fe0891 100644 --- a/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java +++ b/src/main/java/nl/andrewlalis/erme/view/EditorMenuBar.java @@ -12,7 +12,10 @@ 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; this.add(this.buildFileMenu()); this.add(this.buildEditMenu()); this.add(this.buildHelpMenu()); @@ -45,6 +48,9 @@ public class EditorMenuBar extends JMenuBar { private JMenu buildHelpMenu() { JMenu menu = new JMenu("Help"); menu.add(InstructionsAction.getInstance()); + if (this.includeAdminActions) { + menu.add(MappingAlgorithmHelpAction.getInstance()); + } menu.add(LoadSampleModelAction.getInstance()); menu.add(AboutAction.getInstance()); return menu; diff --git a/src/main/resources/html/er_mapping_algorithm.html b/src/main/resources/html/er_mapping_algorithm.html new file mode 100644 index 0000000..d6ea0b3 --- /dev/null +++ b/src/main/resources/html/er_mapping_algorithm.html @@ -0,0 +1,101 @@ + + +
+ ++ Written by @andrewlalis. Adapted from Fundamentals of Database Systems, 7th Edition. +
+ ++ For each regular (strong) entity type E in the ER schema, create a relation R that includes all the simple attributes of E. Include only the simple component attributes of a composite attribute. Choose one of the key attributes of E as the primary key for R. If the chosen key is a composite, then the set of simple attributes that form it will together form the primary key of R. +
++ If multiple keys were identified for E during the design of the schema, then the information describing the attributes that form each additional key should be kept in order to specify additional (unique) keys of the relation R. Knowledge about keys is also kept for indexing purposes and other types of analyses. +
+ ++ For each weak entity type W in the ER schema with owner entity type E, create a relation R and include all simple attributes (or simple components of composite attributes) of W as attributes of R. In addition, include as foreign key attributes of R, the primary key attribute(s) of the relation(s) that correspond to the owner entity type(s); this takes care of mapping the identifying relationship type of W. The primary key of R is the combination of the primary key(s) of the owner(s) and the partial key of the weak entity type W, if any. If there is a weak entity type E2 whose owner is also a weak entity type E1, then E1 should be mapped first, to determine the primary key(s) that will be required by E2. +
+ ++ For each binary 1:1 relationship type R in the ER schema, identify the relations S and T that correspond to the entity types participating in R. There are three possible approaches, the first of which is the most useful and should be followed unless special conditions exist: +
++ There are two possible approaches, the first of which is generally preferred as it reduces the number of tables. +
++ The only option for M:N relationships in the traditional relational model is the relationship relation. For each binary M:N relationship type R, create a new relation S to represent R. Include foreign key attributes in S for the primary keys of both participating entities. The combination of both foreign keys forms the primary key of S. Also include any simple attributes of R as attributes of S. +
+ ++ For each multivalued attribute A from an entity E, create a new relation that contains a foreign key to E, and an attribute representing a single instance of A. The primary key of the new relation is the combination of the foreign key and the single attribute. +
+ ++ For each N-ary relationship type R, where n > 2, create a new relationship relation S to represent R. Include as foreign key attributes in S the primary keys of the relations that represent the participating entity types. Also include any simple attributes of the n-ary relationship type (or simple components of the composite attributes) as attributes of S. The primary key of S is usually a combination of all the foreign keys that reference the participating entities (except the foreign keys to entities that participate in the relationship with a cardinality constraint of 1). +
+ ++ There are a few different options for mapping specializations and generalizations. The four most common are given here: +
+NULL
values if there are many separate subclass-specific attributes.
+ + For mapping a union type, we create a surrogate key attribute which should be appended to the relation of any entity that participates in the union. A relation is made for the union type itself, and that relation uses the surrogate key as its primary key. We then also declare the surrogate key in each participating entity relation as a foreign key to the union type's primary key. +
+ + \ No newline at end of file