/**
* Конструктор пустого списка.
*/
public StudentList() {
}
// Определение узла списка
private static class Node {
Student item;
Node next;
Node prev;
Node(Node prev, Student element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
// Методы реализации
/**
* Добавляет указанный элемент в конец списка.
*
* <p>
* Метод такой же, как интерфейсный {@link #add}.
*
* @param e элемент для добавления
*/
private void addLast(Student e) {
final Node l = last;
final Node newNode = new Node(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
}
/**
* Удаляет все ссылки на непустой узел списка x.
*/
Student unlink(Node x) {
final Student element = x.item;
final Node next = x.next;
final Node prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
return element;
}
public void printList() {
for (Node x = first; x != null;) {
Node next = x.next;
System.out.println(x.item.toString());
x = next;
}
}
boolean checkGradeBookUnique(int gradeBook) {
return (find(st -> {
return st.getGradeBook() == gradeBook;
}).size == 0);
}
// Интерфейсные методы
@Override
public int size() {
return size;
}
/**
* Добавляет указанный элемент в конец списка.
*
* <p>
* Такой же как метод {@link #addLast}.
*
* @param e элемент для добавления в список.
* @return {@code true}, так как изменяет список.
* @throws IllegalArgumentException, если добавляемый элемент не может быть
* добавлен из-за совпадения номера зачетной
* книжки.
* @throws NullPointerException, если указанный элемент пустой
*/
@Override
public boolean add(Student e) {
if (e == null) {
throw new NullPointerException();
} else if (!checkGradeBookUnique(e.getGradeBook())) {
throw new IllegalArgumentException();
} else {
addLast(e);
return true;
}
}
/**
* Удаляет первое вхождение указанного элемента их списка, если такой элемент в
* списке есть. Если такого элемента в списке нет, список не меняется. Более
* строгое описание, удаляет элемент с наименьшим индексом {@code i}, такой
* что{@code Objects.equals(o, get(i))} Возвращает {@code true}, если список
* содержал указанный элемент (то есть, этот список был изменен вызовом этого
* метода).
*
* @param o элемент, который будет удален, если он имеется в списке
* @return {@code true}, если список содержал указанный элемент
* @throws ClassCastException, если тип указанного элемента отличается от
* {@code Student}
* @throws NullPointerException, если указанный элемент пустой ({@code null})
*/
@Override
public boolean remove(Object o) {
if (o == null) {
throw new NullPointerException();
} else if (!(o instanceof Student)) {
throw new ClassCastException();
} else {
for (Node x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/**
* Удаляет все элементы списка. После выполнения метода список пустой.
*/
@Override
public void clear() {
// Удаление всех ссылок помогает Сборщику мусора Java
// очистить память от неиспользуемых узлов, даже если
// будет существовать Итератор по списку
for (Node x = first; x != null;) {
Node next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
}
private String outOfBoundsMsg(int index) {
return "Index: " + index + ", Size: " + size;
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
Node node(int index) {
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
public Student get(int index) {
checkElementIndex(index);
return node(index).item;
}
public StudentList find(StudentChecks sc) {
StudentList targetList = new StudentList();
for (Node x = first; x != null; x = x.next) {
if (sc.test(x.item)) {
targetList.add(x.item);
}
}
return targetList;
}
public StudentList readFromFile(File file) throws IOException {
StudentList targetList = new StudentList();
String str;
BufferedReader br = new BufferedReader(new FileReader(file));
int counter = 0;
while ((str = br.readLine()) != null) {
counter++;
if (counter > 1) {
targetList.add(Student.parseString(str));
}
}
br.close();
return targetList;
}
public void saveToFile(File targetFile) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter(targetFile));
// заголовок csv файла
bw.write("Номер зачетной книжки;Имя;Фамилия;Отчество;Дата рождения");
for (Node x = first; x != null;) {
Node next = x.next;
bw.write(System.getProperty("line.separator") + x.item.toString());
x = next;
}
bw.close();
}
}
Файл «StudentListApp.java»
import java.time.LocalDate;
import java.time.Month;
import java.time.format.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import java.awt.Dimension;
import java.awt.Font;
import java.text.NumberFormat;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.event.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.JFormattedTextField;
import javax.swing.JTextField;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.awt.event.ActionEvent;
public class StudentListApp extends JFrame implements TableModelListener {
/**
*
*/
private static final long serialVersionUID = 1L;
// Объявления переменных, доступных внутри класса в целом
private JFormattedTextField formattedTxtGradeBook;
private JTextField txtSurname;
private JTextField txtFirstName;
private JTextField txtSecondName;
private final DateTimeFormatter shortFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
private JFormattedTextField formattedTxtBirthDate;
private JTextArea txtAreaPrompt;
private JButton btnShowAll;
private JScrollPane scrollPane;
private StudentListTableModel tableModel;
private JTable table;
private StudentList list;
private String[] columnNames = { "Номер з/к", "Фамилия", "Имя", "Отчетсво", "Дата рождения" };
// Конструктор, создающий окно GUI приложения
public StudentListApp() {
// Модельный список для отладки приложения
list = new StudentList();
Student st1 = new Student(666111, "Ivanov", "Ivan", "Ivanovich", LocalDate.of(1990, Month.APRIL, 1));
Student st2 = new Student(666112, "Petrov", "Petr", "Petrovich", LocalDate.of(1991, Month.AUGUST, 10));
Student st3 = new Student(666113, "Korableva", "Tatiana", "Petrovna", LocalDate.of(1990, Month.SEPTEMBER, 7));
Student st4 = new Student(666114, "Korableva", "Natalia", "Petrovna", LocalDate.of(1989, Month.MAY, 5));
list.add(st1);
list.add(st2);
list.add(st3);
list.add(st4);
setTitle("Student List App");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(null);
// Объявления и свойства элементов GUI
JLabel lblGradeBook = new JLabel("Номер зачетной книжки:");
lblGradeBook.setHorizontalAlignment(SwingConstants.RIGHT);
lblGradeBook.setFont(new Font("Tahoma", Font.PLAIN, 12));
lblGradeBook.setBounds(10, 50, 149, 14);
getContentPane().add(lblGradeBook);
JLabel lblSurname = new JLabel("Фамилия:");
lblSurname.setHorizontalAlignment(SwingConstants.RIGHT);
lblSurname.setFont(new Font("Tahoma", Font.PLAIN, 12));
lblSurname.setBounds(10, 82, 149, 14);
getContentPane().add(lblSurname);
JLabel lblFirstName = new JLabel("Имя:");
lblFirstName.setHorizontalAlignment(SwingConstants.RIGHT);
lblFirstName.setFont(new Font("Tahoma", Font.PLAIN, 12));
lblFirstName.setBounds(10, 114, 149, 14);
getContentPane().add(lblFirstName);
JLabel lblSecondName = new JLabel("Отчество:");
lblSecondName.setHorizontalAlignment(SwingConstants.RIGHT);
lblSecondName.setFont(new Font("Tahoma", Font.PLAIN, 12));
lblSecondName.setBounds(10, 146, 149, 14);
getContentPane().add(lblSecondName);
JLabel lblBirthDate = new JLabel("Дата рождения:");
lblBirthDate.setHorizontalAlignment(SwingConstants.RIGHT);
lblBirthDate.setFont(new Font("Tahoma", Font.PLAIN, 12));
lblBirthDate.setBounds(10, 175, 149, 14);
getContentPane().add(lblBirthDate);
JLabel lblNewLabel = new JLabel("Приложение для ведения списка студентов");
lblNewLabel.setFont(new Font("Tahoma", Font.BOLD, 14));
lblNewLabel.setHorizontalAlignment(SwingConstants.CENTER);
lblNewLabel.setBounds(10, 11, 664, 24);
getContentPane().add(lblNewLabel);
formattedTxtBirthDate = new JFormattedTextField(shortFormatter);
formattedTxtBirthDate.setBounds(169, 174, 309, 20);
getContentPane().add(formattedTxtBirthDate);
formattedTxtBirthDate.setValue(LocalDate.now().format(shortFormatter));
NumberFormat gradeBookFormat = NumberFormat.getIntegerInstance();
gradeBookFormat.setParseIntegerOnly(true);
gradeBookFormat.setGroupingUsed(false);
formattedTxtGradeBook = new JFormattedTextField(gradeBookFormat);
formattedTxtGradeBook.setBounds(169, 45, 309, 20);
getContentPane().add(formattedTxtGradeBook);
formattedTxtGradeBook.setColumns(10);
txtSurname = new JTextField();
txtSurname.setColumns(10);
txtSurname.setBounds(169, 78, 309, 20);
getContentPane().add(txtSurname);
txtFirstName = new JTextField();
txtFirstName.setColumns(10);
txtFirstName.setBounds(169, 111, 309, 20);
getContentPane().add(txtFirstName);
txtSecondName = new JTextField();
txtSecondName.setColumns(10);
txtSecondName.setBounds(169, 144, 309, 20);
getContentPane().add(txtSecondName);
JButton btnAdd = new JButton("Добавить");
btnAdd.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addStudent();
}
});
btnAdd.setFont(new Font("Tahoma", Font.BOLD, 12));
btnAdd.setToolTipText("Добавить студента в список");
btnAdd.setBounds(532, 42, 142, 23);
getContentPane().add(btnAdd);
JButton btnRemove = new JButton("Удалить");
btnRemove.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeStudent();
}
});
btnRemove.setFont(new Font("Tahoma", Font.BOLD, 12));
btnRemove.setToolTipText("Удалить студента из списка");
btnRemove.setBounds(532, 75, 142, 23);
getContentPane().add(btnRemove);
JButton btnFind = new JButton("Найти");
btnFind.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
findStudent();
}
});
btnFind.setFont(new Font("Tahoma", Font.BOLD, 12));
btnFind.setToolTipText("Найти таких студентов");
btnFind.setBounds(532, 108, 142, 23);
getContentPane().add(btnFind);
JButton btnClear = new JButton("Очистить список");
btnClear.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
clear();
}
});
btnClear.setFont(new Font("Tahoma", Font.BOLD, 12));
btnClear.setToolTipText("Удалить всех студентов из списка");
btnClear.setBounds(532, 141, 142, 23);
getContentPane().add(btnClear);
JButton btnExport = new JButton("Экспорт");
btnExport.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
exportToFile();
}
});
btnExport.setFont(new Font("Tahoma", Font.BOLD, 12));
btnExport.setToolTipText("Выгрузить список в файл");
btnExport.setBounds(532, 171, 142, 23);
getContentPane().add(btnExport);
JButton btnImport = new JButton("Импорт");
btnImport.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
importFromFile();
}
});
btnImport.setToolTipText("Загрузить список из файла");
btnImport.setFont(new Font("Tahoma", Font.BOLD, 12));
btnImport.setBounds(532, 205, 142, 23);
getContentPane().add(btnImport);
txtAreaPrompt = new JTextArea("");
txtAreaPrompt.setFont(new Font("Tahoma", Font.BOLD, 12));
txtAreaPrompt.setBounds(10, 205, 468, 62);
txtAreaPrompt.setWrapStyleWord(true);
txtAreaPrompt.setLineWrap(true);
txtAreaPrompt.setOpaque(false);
txtAreaPrompt.setEditable(false);
txtAreaPrompt.setFocusable(false);
txtAreaPrompt.setBackground(UIManager.getColor("Label.background"));
txtAreaPrompt.setFont(UIManager.getFont("Label.font"));
txtAreaPrompt.setBorder(UIManager.getBorder("Label.border"));
getContentPane().add(txtAreaPrompt);
btnShowAll = new JButton("Показать все");
btnShowAll.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
drawTable(list, columnNames);
}
});
btnShowAll.setFont(new Font("Tahoma", Font.BOLD, 12));
btnShowAll.setBounds(544, 427, 130, 23);
getContentPane().add(btnShowAll);
scrollPane = new JScrollPane();
scrollPane.setBounds(15, 278, 650, 130);
getContentPane().add(scrollPane);
drawTable(list, columnNames);
}
// Описания методов для работы GUI
public void clearFields() {
formattedTxtGradeBook.setText("");
// formattedTxtGradeBook.requestFocus();
txtSurname.setText("");
txtFirstName.setText("");
txtSecondName.setText("");
formattedTxtBirthDate.setText("");
}
public void addStudent() {
int gradeBook;
String surname;
String firstName;
String secondName;
LocalDate birthDate;
String message = "";
if (!(formattedTxtGradeBook.getText().isBlank() || txtSurname.getText().isBlank()
|| txtFirstName.getText().isBlank() || txtSecondName.getText().isBlank()
|| formattedTxtBirthDate.getText().isBlank())) {
gradeBook = Integer.parseInt(formattedTxtGradeBook.getText());
surname = txtSecondName.getText();
firstName = txtFirstName.getText();
secondName = txtSecondName.getText();
birthDate = LocalDate.parse(formattedTxtBirthDate.getText(), shortFormatter);
try {
list.add(new Student(gradeBook, surname, firstName, secondName, birthDate));
message = "Студент добавлен в список.";
} catch (IllegalArgumentException e) {
message = "Студент не добавлен в список. Номер зачетной книжки студента должен быть уникальным. Проверьте данные.";
}
} else {
message = "Все поля должны быть заполнены для добавления студента в список";
}
txtAreaPrompt.setText(message);
((AbstractTableModel) table.getModel()).fireTableDataChanged();
clearFields();
}
/**
* Удалить выбранного студента
*/
public void removeStudent() {
String message = "";
int selectedRow = table.getSelectedRow();
if (selectedRow == -1) {
message = "Выберите ниже в таблице студентов студента, которого хотите удалить.";
} else {
message = (String) (table.getValueAt(selectedRow, 0));
Student st = new Student(Integer.parseInt(message), "", "", "", LocalDate.now());
list.remove(st);
message = "Студент с номером зачетной книжки " + message + " удален. Ниже обновленный список студентов.";
}
txtAreaPrompt.setText(message);
drawTable(list, columnNames);
clearFields();
}
/**
* Найти выбранного студента и обновить таблицу
*/
public void findStudent() {
String message = "Вы выполнили фильтрацию студентов. Чтобы вновь посмотреть весь список, нажмите кнопку \""
+ btnShowAll.getText() + "\"";
StudentList filteredList;
int findType = getFindType();
// Поиск по номеру зачетной книжки
switch (findType) {
case 1:
filteredList = list.find(st -> {
return st.getGradeBook() == Integer.parseInt(formattedTxtGradeBook.getText());
});
drawTable(filteredList, columnNames);
break;
case 2:
filteredList = list.find(st -> {
return st.getSurname().equals(txtSurname.getText()) && st.getFirstName().equals(txtFirstName.getText())
&& st.getSecondName().equals(txtSecondName.getText());
});
drawTable(filteredList, columnNames);
break;
case 3:
filteredList = list.find(st -> {
return st.getBirthDate().equals(LocalDate.parse(formattedTxtBirthDate.getText(), shortFormatter));
});
drawTable(filteredList, columnNames);
break;
case 4:
filteredList = list.find(st -> {
return st.getGradeBook() == Integer.parseInt(formattedTxtGradeBook.getText())
&& st.getSurname().equals(txtSurname.getText())
&& st.getFirstName().equals(txtFirstName.getText())
&& st.getSecondName().equals(txtSecondName.getText())
&& st.getBirthDate().equals(LocalDate.parse(formattedTxtBirthDate.getText(), shortFormatter));
});
drawTable(filteredList, columnNames);
break;
default:
message = "Для поиска студента внесите или номер зачетной книжки, или ФИО, или дату рождения для поиска студента (можно все вместе).";
txtAreaPrompt.setText(message);
}
clearFields();
}
/**
* По заполненности полей определяет вид поиска студентов в списке: 1 - Поиск по
* номеру зачетной книжки; 2 - Поиск по ФИО; 3 - Поиск по дате рождения; 4 -
* Поиск по всем параметрам
*
* @return номер вида поиска типа {@code int}
*/
public int getFindType() {
if ((!formattedTxtGradeBook.getText().isBlank()) && txtSurname.getText().isBlank()
&& txtFirstName.getText().isBlank() && txtSecondName.getText().isBlank()
&& formattedTxtBirthDate.getText().isBlank()) {
return 1;