diff --git a/.gitignore b/.gitignore index 2c6eb3893..5ff6309b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,38 @@ -/.metadata -/robots/.settings -/robots/bin -eclipse.bat \ No newline at end of file +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..26d33521a --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml new file mode 100644 index 000000000..c5dcbb130 --- /dev/null +++ b/.idea/checkstyle-idea.xml @@ -0,0 +1,16 @@ + + + + 10.13.0 + JavaOnly + true + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 000000000..aa00ffab7 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..2a60c9b9c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 000000000..2b63946d5 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index d79735aee..000000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Robots -The project to learn OO design concepts and MDI application development in Java diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..866f4f846 --- /dev/null +++ b/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + org.example + Robots + 1.0-SNAPSHOT + + + 20 + 20 + UTF-8 + + + + + junit + junit + 4.12 + test + + + junit + junit + 4.13.2 + compile + + + org.junit.jupiter + junit-jupiter + RELEASE + compile + + + + + \ No newline at end of file diff --git a/robots/.classpath b/robots/.classpath deleted file mode 100644 index fceb4801b..000000000 --- a/robots/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/robots/.gitignore b/robots/.gitignore deleted file mode 100644 index 2757ffa76..000000000 --- a/robots/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/bin/ -/.settings/ diff --git a/robots/.project b/robots/.project deleted file mode 100644 index 78e165663..000000000 --- a/robots/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - Robots - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/robots/src/gui/MainApplicationFrame.java b/robots/src/gui/MainApplicationFrame.java deleted file mode 100644 index 62e943ee1..000000000 --- a/robots/src/gui/MainApplicationFrame.java +++ /dev/null @@ -1,156 +0,0 @@ -package gui; - -import java.awt.Dimension; -import java.awt.Toolkit; -import java.awt.event.KeyEvent; - -import javax.swing.JDesktopPane; -import javax.swing.JFrame; -import javax.swing.JInternalFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; - -import log.Logger; - -/** - * Что требуется сделать: - * 1. Метод создания меню перегружен функционалом и трудно читается. - * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс). - * - */ -public class MainApplicationFrame extends JFrame -{ - private final JDesktopPane desktopPane = new JDesktopPane(); - - public MainApplicationFrame() { - //Make the big window be indented 50 pixels from each edge - //of the screen. - int inset = 50; - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - setBounds(inset, inset, - screenSize.width - inset*2, - screenSize.height - inset*2); - - setContentPane(desktopPane); - - - LogWindow logWindow = createLogWindow(); - addWindow(logWindow); - - GameWindow gameWindow = new GameWindow(); - gameWindow.setSize(400, 400); - addWindow(gameWindow); - - setJMenuBar(generateMenuBar()); - setDefaultCloseOperation(EXIT_ON_CLOSE); - } - - protected LogWindow createLogWindow() - { - LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); - logWindow.setLocation(10,10); - logWindow.setSize(300, 800); - setMinimumSize(logWindow.getSize()); - logWindow.pack(); - Logger.debug("Протокол работает"); - return logWindow; - } - - protected void addWindow(JInternalFrame frame) - { - desktopPane.add(frame); - frame.setVisible(true); - } - -// protected JMenuBar createMenuBar() { -// JMenuBar menuBar = new JMenuBar(); -// -// //Set up the lone menu. -// JMenu menu = new JMenu("Document"); -// menu.setMnemonic(KeyEvent.VK_D); -// menuBar.add(menu); -// -// //Set up the first menu item. -// JMenuItem menuItem = new JMenuItem("New"); -// menuItem.setMnemonic(KeyEvent.VK_N); -// menuItem.setAccelerator(KeyStroke.getKeyStroke( -// KeyEvent.VK_N, ActionEvent.ALT_MASK)); -// menuItem.setActionCommand("new"); -//// menuItem.addActionListener(this); -// menu.add(menuItem); -// -// //Set up the second menu item. -// menuItem = new JMenuItem("Quit"); -// menuItem.setMnemonic(KeyEvent.VK_Q); -// menuItem.setAccelerator(KeyStroke.getKeyStroke( -// KeyEvent.VK_Q, ActionEvent.ALT_MASK)); -// menuItem.setActionCommand("quit"); -//// menuItem.addActionListener(this); -// menu.add(menuItem); -// -// return menuBar; -// } - - private JMenuBar generateMenuBar() - { - JMenuBar menuBar = new JMenuBar(); - - JMenu lookAndFeelMenu = new JMenu("Режим отображения"); - lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); - lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( - "Управление режимом отображения приложения"); - - { - JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); - systemLookAndFeel.addActionListener((event) -> { - setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - this.invalidate(); - }); - lookAndFeelMenu.add(systemLookAndFeel); - } - - { - JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_S); - crossplatformLookAndFeel.addActionListener((event) -> { - setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); - this.invalidate(); - }); - lookAndFeelMenu.add(crossplatformLookAndFeel); - } - - JMenu testMenu = new JMenu("Тесты"); - testMenu.setMnemonic(KeyEvent.VK_T); - testMenu.getAccessibleContext().setAccessibleDescription( - "Тестовые команды"); - - { - JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); - addLogMessageItem.addActionListener((event) -> { - Logger.debug("Новая строка"); - }); - testMenu.add(addLogMessageItem); - } - - menuBar.add(lookAndFeelMenu); - menuBar.add(testMenu); - return menuBar; - } - - private void setLookAndFeel(String className) - { - try - { - UIManager.setLookAndFeel(className); - SwingUtilities.updateComponentTreeUI(this); - } - catch (ClassNotFoundException | InstantiationException - | IllegalAccessException | UnsupportedLookAndFeelException e) - { - // just ignore - } - } -} diff --git a/robots/src/log/LogWindowSource.java b/robots/src/log/LogWindowSource.java deleted file mode 100644 index ca0ce4426..000000000 --- a/robots/src/log/LogWindowSource.java +++ /dev/null @@ -1,89 +0,0 @@ -package log; - -import java.util.ArrayList; -import java.util.Collections; - -/** - * Что починить: - * 1. Этот класс порождает утечку ресурсов (связанные слушатели оказываются - * удерживаемыми в памяти) - * 2. Этот класс хранит активные сообщения лога, но в такой реализации он - * их лишь накапливает. Надо же, чтобы количество сообщений в логе было ограничено - * величиной m_iQueueLength (т.е. реально нужна очередь сообщений - * ограниченного размера) - */ -public class LogWindowSource -{ - private int m_iQueueLength; - - private ArrayList m_messages; - private final ArrayList m_listeners; - private volatile LogChangeListener[] m_activeListeners; - - public LogWindowSource(int iQueueLength) - { - m_iQueueLength = iQueueLength; - m_messages = new ArrayList(iQueueLength); - m_listeners = new ArrayList(); - } - - public void registerListener(LogChangeListener listener) - { - synchronized(m_listeners) - { - m_listeners.add(listener); - m_activeListeners = null; - } - } - - public void unregisterListener(LogChangeListener listener) - { - synchronized(m_listeners) - { - m_listeners.remove(listener); - m_activeListeners = null; - } - } - - public void append(LogLevel logLevel, String strMessage) - { - LogEntry entry = new LogEntry(logLevel, strMessage); - m_messages.add(entry); - LogChangeListener [] activeListeners = m_activeListeners; - if (activeListeners == null) - { - synchronized (m_listeners) - { - if (m_activeListeners == null) - { - activeListeners = m_listeners.toArray(new LogChangeListener [0]); - m_activeListeners = activeListeners; - } - } - } - for (LogChangeListener listener : activeListeners) - { - listener.onLogChanged(); - } - } - - public int size() - { - return m_messages.size(); - } - - public Iterable range(int startFrom, int count) - { - if (startFrom < 0 || startFrom >= m_messages.size()) - { - return Collections.emptyList(); - } - int indexTo = Math.min(startFrom + count, m_messages.size()); - return m_messages.subList(startFrom, indexTo); - } - - public Iterable all() - { - return m_messages; - } -} diff --git a/robots/src/gui/GameVisualizer.java b/src/main/java/org/example/gui/GameVisualizer.java similarity index 96% rename from robots/src/gui/GameVisualizer.java rename to src/main/java/org/example/gui/GameVisualizer.java index f82cfd8f8..96952cb29 100644 --- a/robots/src/gui/GameVisualizer.java +++ b/src/main/java/org/example/gui/GameVisualizer.java @@ -139,6 +139,9 @@ private void moveRobot(double velocity, double angularVelocity, double duration) { newY = m_robotPositionY + velocity * duration * Math.sin(m_robotDirection); } + // Проверяем, не выходит ли червячок за границы экрана + newX = applyLimits(newX, 0, getWidth()); + newY = applyLimits(newY, 0, getHeight()); m_robotPositionX = newX; m_robotPositionY = newY; double newDirection = asNormalizedRadians(m_robotDirection + angularVelocity * duration); diff --git a/robots/src/gui/GameWindow.java b/src/main/java/org/example/gui/GameWindow.java similarity index 67% rename from robots/src/gui/GameWindow.java rename to src/main/java/org/example/gui/GameWindow.java index ecb63c00f..5cc47c187 100644 --- a/robots/src/gui/GameWindow.java +++ b/src/main/java/org/example/gui/GameWindow.java @@ -2,19 +2,23 @@ import java.awt.BorderLayout; -import javax.swing.JInternalFrame; -import javax.swing.JPanel; +import javax.swing.*; +import javax.swing.event.InternalFrameEvent; +import javax.swing.event.InternalFrameListener; -public class GameWindow extends JInternalFrame +public class GameWindow extends inter { private final GameVisualizer m_visualizer; - public GameWindow() + public GameWindow() { super("Игровое поле", true, true, true, true); + m_visualizer = new GameVisualizer(); + JPanel panel = new JPanel(new BorderLayout()); panel.add(m_visualizer, BorderLayout.CENTER); getContentPane().add(panel); pack(); } + } diff --git a/robots/src/gui/LogWindow.java b/src/main/java/org/example/gui/LogWindow.java similarity index 70% rename from robots/src/gui/LogWindow.java rename to src/main/java/org/example/gui/LogWindow.java index 723d3e2fc..429a4f960 100644 --- a/robots/src/gui/LogWindow.java +++ b/src/main/java/org/example/gui/LogWindow.java @@ -1,29 +1,29 @@ package gui; -import java.awt.BorderLayout; -import java.awt.EventQueue; -import java.awt.TextArea; +import java.awt.*; +import java.awt.event.WindowListener; -import javax.swing.JInternalFrame; -import javax.swing.JPanel; +import javax.swing.*; +import javax.swing.event.InternalFrameEvent; +import javax.swing.event.InternalFrameListener; import log.LogChangeListener; import log.LogEntry; import log.LogWindowSource; -public class LogWindow extends JInternalFrame implements LogChangeListener +public class LogWindow extends inter implements LogChangeListener { private LogWindowSource m_logSource; private TextArea m_logContent; - public LogWindow(LogWindowSource logSource) + public LogWindow(LogWindowSource logSource) { super("Протокол работы", true, true, true, true); m_logSource = logSource; m_logSource.registerListener(this); m_logContent = new TextArea(""); m_logContent.setSize(200, 500); - + JPanel panel = new JPanel(new BorderLayout()); panel.add(m_logContent, BorderLayout.CENTER); getContentPane().add(panel); @@ -31,6 +31,8 @@ public LogWindow(LogWindowSource logSource) updateLogContent(); } + + private void updateLogContent() { StringBuilder content = new StringBuilder(); @@ -41,7 +43,13 @@ private void updateLogContent() m_logContent.setText(content.toString()); m_logContent.invalidate(); } - + + @Override + public void doDefaultCloseAction() { + m_logSource.unregisterListener(this); + super.doDefaultCloseAction(); + } + @Override public void onLogChanged() { diff --git a/src/main/java/org/example/gui/MainApplicationFrame.java b/src/main/java/org/example/gui/MainApplicationFrame.java new file mode 100644 index 000000000..112899997 --- /dev/null +++ b/src/main/java/org/example/gui/MainApplicationFrame.java @@ -0,0 +1,95 @@ +package gui; + +import java.awt.*; +import java.awt.event.WindowAdapter; + +import javax.swing.*; + + +import log.Logger; + + + +/** + * Что требуется сделать: + * 1. Метод создания меню перегружен функционалом и трудно читается. + * Следует разделить его на серию более простых методов (или вообще выделить отдельный класс). + * + */ + +public class MainApplicationFrame extends JFrame // +{ + private final JDesktopPane desktopPane = new JDesktopPane(); + + public MainApplicationFrame() { + //Make the big window be indented 50 pixels from each edge + //of the screen. + int inset = 50; + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + setBounds(inset, inset, + screenSize.width - inset*2, + screenSize.height - inset*2); + + setContentPane(desktopPane); + + + LogWindow logWindow = createLogWindow(); + addWindow(logWindow); + + GameWindow gameWindow = new GameWindow(); + gameWindow.setSize(400, 400); + addWindow(gameWindow); + + MenuBar menuBar = new MenuBar(this); + setJMenuBar(menuBar.generateMenuBar()); + setDefaultCloseOperation(EXIT_ON_CLOSE); + } + + protected LogWindow createLogWindow() + { + LogWindow logWindow = new LogWindow(Logger.getDefaultLogSource()); + logWindow.setLocation(10,10); + logWindow.setSize(300, 800); + setMinimumSize(logWindow.getSize()); + logWindow.pack(); + Logger.debug("Протокол работает"); + return logWindow; + } + + protected void addWindow(JInternalFrame frame) + { + desktopPane.add(frame); + frame.setVisible(true); + } + +// protected JMenuBar createMenuBar() { +// JMenuBar menuBar = new JMenuBar(); +// +// //Set up the lone menu.(Настройте одиночное меню.) +// JMenu menu = new JMenu("Document"); +// menu.setMnemonic(KeyEvent.VK_D); +// menuBar.add(menu); +// +// //Set up the first menu item.(Настройте первый пункт меню.) +// JMenuItem menuItem = new JMenuItem("New"); +// menuItem.setMnemonic(KeyEvent.VK_N); +// menuItem.setAccelerator(KeyStroke.getKeyStroke( +// KeyEvent.VK_N, ActionEvent.ALT_MASK)); +// menuItem.setActionCommand("new"); +//// menuItem.addActionListener(this); +// menu.add(menuItem); +// +// //Set up the second menu item.(Настройте второй пункт меню.) +// menuItem = new JMenuItem("Quit"); +// menuItem.setMnemonic(KeyEvent.VK_Q); +// menuItem.setAccelerator(KeyStroke.getKeyStroke( +// KeyEvent.VK_Q, ActionEvent.ALT_MASK)); +// menuItem.setActionCommand("quit"); +//// menuItem.addActionListener(this); +// menu.add(menuItem); +// +// return menuBar; +// } + + +} diff --git a/src/main/java/org/example/gui/MenuBar.java b/src/main/java/org/example/gui/MenuBar.java new file mode 100644 index 000000000..fae7d6134 --- /dev/null +++ b/src/main/java/org/example/gui/MenuBar.java @@ -0,0 +1,121 @@ +package gui; + +import log.Logger; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.Locale; +import java.util.ResourceBundle; + +public class MenuBar { + static MainApplicationFrame mainFrame; // Используем вместо this, т.к. this имеет ввиду MainApplicationFrame объект + // определяем MenuBar передавая ему аргумент MainApplicationFrame + public MenuBar(MainApplicationFrame mainFrame){ + this.mainFrame = mainFrame; + } + public static JMenuBar generateMenuBar() { + JMenuBar menuBar = new JMenuBar(); + + menuBar.add(createLookAndFeelMenu()); + menuBar.add(createTestMenu()); + //menuBar.add(createSettingMenu()); + menuBar.add(createExitButton()); + return menuBar; + } + +// private static JMenu createSettingMenu() { +// ResourceBundle rb = ResourceBundle.getBundle("Language", Locale.getDefault()); +// JMenu settingMenu = new JMenu("Настройки"); +// JMenu settingLanguage = new JMenu(rb.getString("Language")); +// Locale ru = new Locale("ru", "RU"); +// Locale en = new Locale("en", "US"); +// +// { +// JMenuItem english = new JMenuItem("Английский", KeyEvent.VK_S); +// english.addActionListener((event) -> { +// Locale.setDefault(new Locale("en", "US")); +// }); +// settingLanguage.add(english); +// } +// +// { +// JMenuItem russian = new JMenuItem("Русский"); +// +// settingLanguage.add(russian); +// } +// +// settingMenu.add(settingLanguage); +// return settingMenu; +// } + + private static JMenu createLookAndFeelMenu() { + + JMenu lookAndFeelMenu = new JMenu("Режим отображения"); + lookAndFeelMenu.setMnemonic(KeyEvent.VK_V); + lookAndFeelMenu.getAccessibleContext().setAccessibleDescription( + "Управление режимом отображения приложения"); + + { + JMenuItem systemLookAndFeel = new JMenuItem("Системная схема", KeyEvent.VK_S); + systemLookAndFeel.addActionListener((event) -> { + setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + mainFrame.invalidate(); + }); + lookAndFeelMenu.add(systemLookAndFeel); + } + + { + JMenuItem crossplatformLookAndFeel = new JMenuItem("Универсальная схема", KeyEvent.VK_S); + crossplatformLookAndFeel.addActionListener((event) -> { + setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + mainFrame.invalidate(); + }); + lookAndFeelMenu.add(crossplatformLookAndFeel); + } + + return lookAndFeelMenu; + } + + + private static JMenu createTestMenu() { + JMenu testMenu = new JMenu("Тесты"); + testMenu.setMnemonic(KeyEvent.VK_T); + testMenu.getAccessibleContext().setAccessibleDescription( + "Тестовые команды"); + + { + JMenuItem addLogMessageItem = new JMenuItem("Сообщение в лог", KeyEvent.VK_S); + addLogMessageItem.addActionListener((event) -> { + Logger.debug("Новая строка"); + }); + testMenu.add(addLogMessageItem); + } + return testMenu; + } + + private static JMenu createExitButton() { + JMenu menu = new JMenu("Выйти"); + menu.setMnemonic(KeyEvent.VK_T);//KeyEvent.VK_T + + { + JMenuItem exit1 = new JMenuItem("Выйти", KeyEvent.VK_S); + exit1.setFocusable(false); + exit1.addActionListener((event) -> { + System.exit(0); + }); + menu.add(exit1); + } + return menu; + } + + private static void setLookAndFeel(String className) { + try { + UIManager.setLookAndFeel(className); + SwingUtilities.updateComponentTreeUI(mainFrame); + } catch (ClassNotFoundException | InstantiationException + | IllegalAccessException | UnsupportedLookAndFeelException e) { + } + } +} diff --git a/robots/src/gui/RobotsProgram.java b/src/main/java/org/example/gui/RobotsProgram.java similarity index 100% rename from robots/src/gui/RobotsProgram.java rename to src/main/java/org/example/gui/RobotsProgram.java diff --git a/src/main/java/org/example/gui/inter.java b/src/main/java/org/example/gui/inter.java new file mode 100644 index 000000000..9aee57e09 --- /dev/null +++ b/src/main/java/org/example/gui/inter.java @@ -0,0 +1,62 @@ +package gui; + +import javax.swing.*; +import javax.swing.event.InternalFrameEvent; +import javax.swing.event.InternalFrameListener; + +public abstract class inter extends JInternalFrame{ + + public inter(String str, boolean b, boolean b1, boolean b2, boolean b3){ + super(str, b, b1, b2, b3); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addInternalFrameListener(new InternalFrameListener() { + + @Override + public void internalFrameOpened(InternalFrameEvent e) { + + } + + @Override + public void internalFrameClosing(InternalFrameEvent event) { + Object[] options = { "Да", "Нет!" }; + int n = JOptionPane + .showOptionDialog(event.getInternalFrame(), "Закрыть окно?", + "Подтверждение", JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, + options[0]); + if (n == 0) { + event.getInternalFrame().setVisible(false); + setDefaultCloseOperation(EXIT_ON_CLOSE); + } + } + + @Override + public void internalFrameClosed(InternalFrameEvent e) { + + } + + @Override + public void internalFrameIconified(InternalFrameEvent e) { + + } + + @Override + public void internalFrameDeiconified(InternalFrameEvent e) { + + } + + @Override + public void internalFrameActivated(InternalFrameEvent e) { + + } + + @Override + public void internalFrameDeactivated(InternalFrameEvent e) { + + } + + }); + } + + +} diff --git a/robots/src/log/LogChangeListener.java b/src/main/java/org/example/log/LogChangeListener.java similarity index 100% rename from robots/src/log/LogChangeListener.java rename to src/main/java/org/example/log/LogChangeListener.java diff --git a/robots/src/log/LogEntry.java b/src/main/java/org/example/log/LogEntry.java similarity index 100% rename from robots/src/log/LogEntry.java rename to src/main/java/org/example/log/LogEntry.java diff --git a/robots/src/log/LogLevel.java b/src/main/java/org/example/log/LogLevel.java similarity index 100% rename from robots/src/log/LogLevel.java rename to src/main/java/org/example/log/LogLevel.java diff --git a/src/main/java/org/example/log/LogWindowSource.java b/src/main/java/org/example/log/LogWindowSource.java new file mode 100644 index 000000000..b7a6928b3 --- /dev/null +++ b/src/main/java/org/example/log/LogWindowSource.java @@ -0,0 +1,99 @@ +package log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; + +/** + * пофикшены: + * 1. утечка ресурсов (связанные слушатели оказываются удерживаемыми в памяти) + * 2. ограничения; раньше класс хранил активные сообщения лога, но в такой реализации он + * их лишь накапливал. Сейчас количество сообщений в логе было ограничено + * величиной m_iQueueLength (т.е. реально нужна очередь сообщений + * ограниченного размера) + * + * изменения: + * Используется ArrayBlockingQueue для хранения сообщений лога, что позволяет избежать утечек ресурсов и ограничить + * размер лога величиной m_iQueueLength. + * Избавлены от необходимости хранить все слушатели в виде массива, который нужно было пересоздавать каждый раз при + * добавлении или удалении слушателя. Вместо этого используется обычный список m_listeners, синхронизированный + * на запись/удаление. + * Используется offer() вместо add() при добавлении сообщения в очередь, чтобы избежать исключения в случае, + * если очередь заполнена. + * Метод size() теперь возвращает размер m_queue. + * Метод range() теперь проверяет, что count неотрицательное число и возвращает пустой список, если startFrom выходит + * за пределы очереди или count равен нулю. + * Метод range() теперь использует метод ArrayList.subList() непосредственно над m_queue, а не создает новый список, + * что улучшает производительность и уменьшает использование памяти. + * Метод all() теперь создает новый список из m_queue, вместо создания списка итератором. + */ +public class LogWindowSource { + private final int m_iQueueLength; + private final ArrayBlockingQueue m_queue; + private final List m_listeners; + private volatile LogChangeListener[] m_activeListeners; + + public LogWindowSource(int iQueueLength) { + m_iQueueLength = iQueueLength; + m_queue = new ArrayBlockingQueue<>(iQueueLength); + m_listeners = new ArrayList<>(); + } + + public void registerListener(LogChangeListener listener) { + synchronized(m_listeners) { + m_listeners.add(listener); + m_activeListeners = null; + } + } + + public void unregisterListener(LogChangeListener listener) { + synchronized(m_listeners) { + m_listeners.remove(listener); + m_activeListeners = null; + } + } + + public void append(LogLevel logLevel, String strMessage) { + boolean added = false; + while (!added) { + LogEntry entry = new LogEntry(logLevel, strMessage); + if (m_queue.offer(entry)) { + added = true; + if (m_queue.size() > m_iQueueLength) { + m_queue.remove(); + } + LogChangeListener[] activeListeners = m_activeListeners; + if (activeListeners == null) { + synchronized (m_listeners) { + if (m_activeListeners == null) { + activeListeners = m_listeners.toArray(new LogChangeListener[0]); + m_activeListeners = activeListeners; + } + } + } + for (LogChangeListener listener : activeListeners) { + listener.onLogChanged(); + } + } else { + m_queue.remove(); + } + } + } + + public int size() { + return m_queue.size(); + } + + public Iterable range(int startFrom, int count) { + if (startFrom < 0 || startFrom >= m_queue.size() || count <= 0) { + return Collections.emptyList(); + } + int indexTo = Math.min(startFrom + count, m_queue.size()); + return new ArrayList<>(m_queue).subList(startFrom, indexTo); + } + + public Iterable all() { + return new ArrayList<>(m_queue); + } +} \ No newline at end of file diff --git a/robots/src/log/Logger.java b/src/main/java/org/example/log/Logger.java similarity index 100% rename from robots/src/log/Logger.java rename to src/main/java/org/example/log/Logger.java diff --git a/src/main/java/org/example/resourses/english.properties b/src/main/java/org/example/resourses/english.properties new file mode 100644 index 000000000..2dfb3ee48 --- /dev/null +++ b/src/main/java/org/example/resourses/english.properties @@ -0,0 +1 @@ +Language=Language \ No newline at end of file diff --git a/src/main/java/org/example/resourses/russian.properties b/src/main/java/org/example/resourses/russian.properties new file mode 100644 index 000000000..2df80d8cd --- /dev/null +++ b/src/main/java/org/example/resourses/russian.properties @@ -0,0 +1 @@ +Language=\u041F\u0440\u043E\u0442\u043E\u043A\u043E\u043B \u0440\u0430\u0431\u043E\u0442\u044B \ No newline at end of file diff --git a/src/main/java/org/example/testsLogs/LogWindowSourceTest.java b/src/main/java/org/example/testsLogs/LogWindowSourceTest.java new file mode 100644 index 000000000..45f845641 --- /dev/null +++ b/src/main/java/org/example/testsLogs/LogWindowSourceTest.java @@ -0,0 +1,63 @@ +package testsLogs; +import log.LogWindowSource; +import log.LogLevel; +import log.LogEntry; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class LogWindowSourceTest { + + @Test + public void testLogAppending() { + LogWindowSource logWindow = new LogWindowSource(5); // Создаем лог с ограничением в 5 сообщений + logWindow.append(LogLevel.Info, "Message 1"); + logWindow.append(LogLevel.Warning, "Message 2"); + logWindow.append(LogLevel.Error, "Message 3"); + + assertEquals(3, logWindow.size()); // Проверяем, что размер лога равен 3 после добавления 3 сообщений + } + + @Test + public void testLogLimit() { + LogWindowSource logWindow = new LogWindowSource(3); // Создаем лог с ограничением в 3 сообщения + logWindow.append(LogLevel.Info, "Message 1"); + logWindow.append(LogLevel.Warning, "Message 2"); + logWindow.append(LogLevel.Error, "Message 3"); + logWindow.append(LogLevel.Debug, "Message 4"); // Превышаем лимит, первое сообщение должно быть удалено + + assertEquals(3, logWindow.size()); // Проверяем, что размер лога равен 3 после добавления 4 сообщений + assertEquals("Message 2", logWindow.all().iterator().next().getMessage()); // Проверяем, что первое сообщение было удалено + } + + @Test + public void testLogRange() { + LogWindowSource logWindow = new LogWindowSource(5); // Создаем лог с ограничением в 5 сообщений + logWindow.append(LogLevel.Info, "Message 1"); + logWindow.append(LogLevel.Warning, "Message 2"); + logWindow.append(LogLevel.Error, "Message 3"); + logWindow.append(LogLevel.Debug, "Message 4"); + logWindow.append(LogLevel.Info, "Message 5"); + + Iterable range = logWindow.range(1, 3); // Получаем диапазон сообщений с индекса 1 до 3 + + int count = 0; + for (LogEntry entry : range) { + count++; + } + assertEquals(3, count); // Проверяем, что в полученном диапазоне содержится 3 сообщения + } + + @Test + public void testLogRangeOutOfBounds() { + LogWindowSource logWindow = new LogWindowSource(5); // Создаем лог с ограничением в 5 сообщений + logWindow.append(LogLevel.Info, "Message 1"); + + Iterable range = logWindow.range(2, 3); // Пытаемся получить диапазон с индекса 2, хотя в логе только 1 сообщение + + int count = 0; + for (LogEntry entry : range) { + count++; + } + assertEquals(0, count); // Проверяем, что полученный диапазон пустой + } +} diff --git a/src/main/resources/resources_en_US.properties b/src/main/resources/resources_en_US.properties new file mode 100644 index 000000000..0c2f442e7 --- /dev/null +++ b/src/main/resources/resources_en_US.properties @@ -0,0 +1 @@ +Language = Language \ No newline at end of file diff --git a/src/main/resources/resources_ru_RU.properties b/src/main/resources/resources_ru_RU.properties new file mode 100644 index 000000000..c2844abdc --- /dev/null +++ b/src/main/resources/resources_ru_RU.properties @@ -0,0 +1 @@ +Language = ttt \ No newline at end of file