diff --git a/src/main/java/com/penapereira/example/constructs/app/AppCommandLineRunner.java b/src/main/java/com/penapereira/example/constructs/app/AppCommandLineRunner.java index 83b4e9d..cc671c3 100644 --- a/src/main/java/com/penapereira/example/constructs/app/AppCommandLineRunner.java +++ b/src/main/java/com/penapereira/example/constructs/app/AppCommandLineRunner.java @@ -4,22 +4,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import com.penapereira.example.constructs.app.properties.Messages; import com.penapereira.example.constructs.app.ui.MainWindow; +import lombok.RequiredArgsConstructor; + @Component +@RequiredArgsConstructor public class AppCommandLineRunner implements CommandLineRunner { private static Logger log = LoggerFactory.getLogger(AppCommandLineRunner.class); - @Autowired - MainWindow main; - - @Autowired - Messages msg; + private final MainWindow main; + private final Messages msg; @Override public void run(String... args) throws Exception { diff --git a/src/main/java/com/penapereira/example/constructs/app/ExamplesCommandLineRunner.java b/src/main/java/com/penapereira/example/constructs/app/ExamplesCommandLineRunner.java index 42946c9..dd3b13d 100644 --- a/src/main/java/com/penapereira/example/constructs/app/ExamplesCommandLineRunner.java +++ b/src/main/java/com/penapereira/example/constructs/app/ExamplesCommandLineRunner.java @@ -5,7 +5,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; @@ -13,19 +12,17 @@ import com.penapereira.example.constructs.app.properties.ApplicationProperties; import com.penapereira.example.constructs.app.properties.Messages; +import lombok.RequiredArgsConstructor; + @Component +@RequiredArgsConstructor public class ExamplesCommandLineRunner implements CommandLineRunner { private static final Logger log = LoggerFactory.getLogger(ExamplesCommandLineRunner.class); - @Autowired - private ApplicationContext ctx; - - @Autowired - Messages msg; - - @Autowired - ApplicationProperties props; + private final ApplicationContext ctx; + private final Messages msg; + private final ApplicationProperties props; @Override public void run(String... args) throws Exception { diff --git a/src/main/java/com/penapereira/example/constructs/app/ui/GuiAppender.java b/src/main/java/com/penapereira/example/constructs/app/ui/GuiAppender.java index 1056fe7..e5e8535 100644 --- a/src/main/java/com/penapereira/example/constructs/app/ui/GuiAppender.java +++ b/src/main/java/com/penapereira/example/constructs/app/ui/GuiAppender.java @@ -5,17 +5,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; +import lombok.RequiredArgsConstructor; @Component +@RequiredArgsConstructor public class GuiAppender extends AppenderBase implements InitializingBean { - @Autowired - private MainWindow mainWindow; + private final OutputSink output; @Override public void afterPropertiesSet() throws Exception { @@ -25,6 +25,6 @@ public void afterPropertiesSet() throws Exception { @Override protected void append(ILoggingEvent eventObject) { - EventQueue.invokeLater(() -> mainWindow.appendOutput(eventObject.getFormattedMessage() + System.lineSeparator())); + EventQueue.invokeLater(() -> output.appendOutput(eventObject.getFormattedMessage() + System.lineSeparator())); } } diff --git a/src/main/java/com/penapereira/example/constructs/app/ui/MainWindow.java b/src/main/java/com/penapereira/example/constructs/app/ui/MainWindow.java index aed7bfe..2573b9f 100644 --- a/src/main/java/com/penapereira/example/constructs/app/ui/MainWindow.java +++ b/src/main/java/com/penapereira/example/constructs/app/ui/MainWindow.java @@ -1,190 +1,186 @@ package com.penapereira.example.constructs.app.ui; +import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Font; -import java.awt.BorderLayout; import java.awt.GridLayout; import java.io.IOException; +import java.util.Map; import javax.imageio.ImageIO; +import javax.swing.BorderFactory; import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JTextArea; -import javax.swing.JCheckBox; import javax.swing.SwingConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; +import com.penapereira.example.constructs.app.ExampleRunnerInterface; import com.penapereira.example.constructs.app.properties.ApplicationProperties; import com.penapereira.example.constructs.app.properties.Messages; -import com.penapereira.example.constructs.app.ExampleRunnerInterface; + +import lombok.RequiredArgsConstructor; @Component -public class MainWindow extends JFrame { +@RequiredArgsConstructor +public class MainWindow extends JFrame implements OutputSink { - private static final Logger log = LoggerFactory.getLogger(MainWindow.class); + private static final Logger log = LoggerFactory.getLogger(MainWindow.class); - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; + + private final Messages msg; + private final ApplicationProperties props; + private final ApplicationContext ctx; private JTextArea outputArea; private JCheckBox preserveLogCheck; + private Map examples; + + public void initializeFrame() { + prepareWindow(); + setContentPane(getMainComponent()); + adjustWindow(); + } + + private void adjustWindow() { + pack(); + setLocationRelativeTo(null); + setSize(getWidth() + 50, getHeight() + 50); + } + + private void prepareWindow() { + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setTitle(msg.getWindowTitle()); + loadIcon(); + } + + private void loadIcon() { + try { + var icon = ImageIO.read(props.getAppIcon().getInputStream()); + setIconImage(icon); + } catch (IOException e) { + log.warn("Could not load app icon", e); + } + } + + private JPanel getMainComponent() { + JPanel mainPanel = new JPanel(new BorderLayout()); + + JPanel infoPanel = new JPanel(new GridLayout(4, 1)); + createCenteredTitle(msg.getGreeting(), infoPanel); + createCenteredLabelOnPanel(msg.getInfo(), infoPanel); + createCenteredHyperlink(msg.getHomeUrl(), infoPanel); + mainPanel.add(infoPanel, BorderLayout.NORTH); + + mainPanel.add(createExamplesPanel(), BorderLayout.WEST); + + outputArea = new JTextArea(10, 40); + outputArea.setEditable(false); + JScrollPane scrollPane = new JScrollPane(outputArea); + scrollPane.setBorder(BorderFactory.createTitledBorder(msg.getOutputTitle())); + + preserveLogCheck = new JCheckBox(msg.getPreserveLog()); + + JPanel outputPanel = new JPanel(new BorderLayout()); + outputPanel.add(preserveLogCheck, BorderLayout.NORTH); + outputPanel.add(scrollPane, BorderLayout.CENTER); + + mainPanel.add(outputPanel, BorderLayout.CENTER); + + return mainPanel; + } + + private void createCenteredTitle(String text, JPanel panel) { + JLabel title = new JLabel(text, JLabel.CENTER); + Font current = title.getFont(); + title.setFont(new Font(current.getName(), current.getStyle(), current.getSize() + 4)); + panel.add(title); + panel.add(new JSeparator(SwingConstants.HORIZONTAL)); + } + + private void createCenteredHyperlink(String text, JPanel panel) { + JLabel hyperlink = new JLabel(text, JLabel.CENTER); + hyperlink.setForeground(Color.decode(props.getLinkColor())); + hyperlink.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + hyperlink.addMouseListener(new HyperlinkMouseListener(props)); + panel.add(hyperlink); + } + + private void createCenteredLabelOnPanel(String text, JPanel panel) { + JLabel label = new JLabel(text); + label.setHorizontalAlignment(JLabel.CENTER); + panel.add(label); + } + + @Override + public void appendOutput(String text) { + if (outputArea != null) { + outputArea.append(text); + outputArea.setCaretPosition(outputArea.getDocument().getLength()); + } + } - @Autowired - Messages msg; - - @Autowired - ApplicationProperties props; - - @Autowired - ApplicationContext ctx; - - private java.util.Map examples; - - public MainWindow() { - super(); - } - - public void initializeFrame() { - prepareWindow(); - setContentPane(getMainComponent()); - adjustWindow(); - } - - private void adjustWindow() { - pack(); - setLocationRelativeTo(null); - setSize(getWidth() + 50, getHeight() + 50); - } - - private void prepareWindow() { - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setTitle(msg.getWindowTitle()); - loadIcon(); - } - - private void loadIcon() { - try { - var icon = ImageIO.read(props.getAppIcon().getInputStream()); - setIconImage(icon); - } catch (IOException e) { - log.warn("Could not load app icon", e); - } - } - - private JPanel getMainComponent() { - JPanel mainPanel = new JPanel(new BorderLayout()); - - JPanel infoPanel = new JPanel(new GridLayout(4, 1)); - createCenteredTitle(msg.getGreeting(), infoPanel); - createCenteredLabelOnPanel(msg.getInfo(), infoPanel); - createCenteredHyperlink(msg.getHomeUrl(), infoPanel); - mainPanel.add(infoPanel, BorderLayout.NORTH); - - mainPanel.add(createExamplesPanel(), BorderLayout.WEST); + private JPanel createExamplesPanel() { + examples = ctx.getBeansOfType(ExampleRunnerInterface.class); - outputArea = new JTextArea(10, 40); - outputArea.setEditable(false); - JScrollPane scrollPane = new JScrollPane(outputArea); - scrollPane.setBorder(javax.swing.BorderFactory.createTitledBorder(msg.getOutputTitle())); + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.setBorder(BorderFactory.createTitledBorder(msg.getExamplesFound())); - preserveLogCheck = new JCheckBox(msg.getPreserveLog()); + JButton allButton = new JButton("Run All"); + allButton.addActionListener(e -> runAllExamples()); + panel.add(allButton); - JPanel outputPanel = new JPanel(new BorderLayout()); - outputPanel.add(preserveLogCheck, BorderLayout.NORTH); - outputPanel.add(scrollPane, BorderLayout.CENTER); + examples.forEach((name, runner) -> { + String clean = name.replaceFirst("ExampleRunner", ""); + JButton btn = new JButton(clean); + btn.addActionListener(e -> runExample(runner)); + panel.add(btn); + }); - mainPanel.add(outputPanel, BorderLayout.CENTER); + return panel; + } - return mainPanel; + private void runExample(ExampleRunnerInterface runner) { + if (!preserveLogCheck.isSelected()) { + outputArea.setText(""); } - - private void createCenteredTitle(String text, JPanel panel) { - JLabel title = new JLabel(text, JLabel.CENTER); - Font current = title.getFont(); - title.setFont(new Font(current.getName(), current.getStyle(), current.getSize() + 4)); - panel.add(title); - panel.add(new JSeparator(SwingConstants.HORIZONTAL)); - } - - private void createCenteredHyperlink(String text, JPanel panel) { - JLabel hyperlink = new JLabel(text, JLabel.CENTER); - hyperlink.setForeground(Color.decode(props.getLinkColor())); - hyperlink.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - hyperlink.addMouseListener(new HyperlinkMouseListener(props)); - panel.add(hyperlink); - } - - private void createCenteredLabelOnPanel(String text, JPanel panel) { - JLabel label = new JLabel(text); - label.setHorizontalAlignment(JLabel.CENTER); - panel.add(label); + new Thread(() -> { + try { + log.trace(msg.getSeparator()); + runner.runExample(); + } catch (Exception e) { + log.error("Error executing example", e); + } + }).start(); + } + + private void runAllExamples() { + if (!preserveLogCheck.isSelected()) { + outputArea.setText(""); } - - public void appendOutput(String text) { - if (outputArea != null) { - outputArea.append(text); - outputArea.setCaretPosition(outputArea.getDocument().getLength()); + new Thread(() -> { + examples.values().forEach(r -> { + try { + log.trace(msg.getSeparator()); + r.runExample(); + } catch (Exception e) { + log.error("Error executing example", e); } - } - - private JPanel createExamplesPanel() { - examples = ctx.getBeansOfType(ExampleRunnerInterface.class); - - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - panel.setBorder(javax.swing.BorderFactory.createTitledBorder(msg.getExamplesFound())); - - javax.swing.JButton allButton = new javax.swing.JButton("Run All"); - allButton.addActionListener(e -> runAllExamples()); - panel.add(allButton); - - examples.forEach((name, runner) -> { - String clean = name.replaceFirst("ExampleRunner", ""); - javax.swing.JButton btn = new javax.swing.JButton(clean); - btn.addActionListener(e -> runExample(runner)); - panel.add(btn); - }); - - return panel; - } - - private void runExample(ExampleRunnerInterface runner) { - if (!preserveLogCheck.isSelected()) { - outputArea.setText(""); - } - new Thread(() -> { - try { - log.trace(msg.getSeparator()); - runner.runExample(); - } catch (Exception e) { - log.error("Error executing example", e); - } - }).start(); - } - - private void runAllExamples() { - if (!preserveLogCheck.isSelected()) { - outputArea.setText(""); - } - new Thread(() -> { - examples.values().forEach(r -> { - try { - log.trace(msg.getSeparator()); - r.runExample(); - } catch (Exception e) { - log.error("Error executing example", e); - } - }); - }).start(); - } + }); + }).start(); + } } diff --git a/src/main/java/com/penapereira/example/constructs/app/ui/OutputSink.java b/src/main/java/com/penapereira/example/constructs/app/ui/OutputSink.java new file mode 100644 index 0000000..8c28dea --- /dev/null +++ b/src/main/java/com/penapereira/example/constructs/app/ui/OutputSink.java @@ -0,0 +1,10 @@ +package com.penapereira.example.constructs.app.ui; + +/** + * Destination for text produced by the application, such as log output + * forwarded by {@link GuiAppender}. + */ +public interface OutputSink { + + void appendOutput(String text); +} diff --git a/src/test/java/com/penapereira/example/constructs/app/ui/GuiAppenderTests.java b/src/test/java/com/penapereira/example/constructs/app/ui/GuiAppenderTests.java index 598d2d9..4d58ec2 100644 --- a/src/test/java/com/penapereira/example/constructs/app/ui/GuiAppenderTests.java +++ b/src/test/java/com/penapereira/example/constructs/app/ui/GuiAppenderTests.java @@ -1,11 +1,8 @@ package com.penapereira.example.constructs.app.ui; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.awt.EventQueue; -import java.lang.reflect.Field; - -import javax.swing.JTextArea; import org.junit.jupiter.api.Test; @@ -14,22 +11,9 @@ class GuiAppenderTests { @Test - void appendsMessageToTextArea() throws Exception { - // Allocate a MainWindow instance without triggering the JFrame constructor - Field theUnsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); - theUnsafeField.setAccessible(true); - sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafeField.get(null); - - MainWindow window = (MainWindow) unsafe.allocateInstance(MainWindow.class); - JTextArea area = new JTextArea(); - Field f = MainWindow.class.getDeclaredField("outputArea"); - f.setAccessible(true); - f.set(window, area); - - GuiAppender appender = new GuiAppender(); - Field mw = GuiAppender.class.getDeclaredField("mainWindow"); - mw.setAccessible(true); - mw.set(appender, window); + void appendsMessageToOutputSink() throws Exception { + StringBuilder output = new StringBuilder(); + GuiAppender appender = new GuiAppender(output::append); appender.start(); LoggingEvent event = new LoggingEvent(); @@ -39,6 +23,6 @@ void appendsMessageToTextArea() throws Exception { appender.doAppend(event); EventQueue.invokeAndWait(() -> {}); - assertEquals("test message" + System.lineSeparator(), area.getText()); + assertEquals("test message" + System.lineSeparator(), output.toString()); } }