Ok, so you have a number of issues, but, lets first focus on "file writing" issues.
A really important place to start is the Basic I/O lesson, just so you can get your head around, well, the basics.
One of the important goals of any solution should be to reduce coupling (between classes). Part of this is a process of reducing the amount "knowledge" (or implementation detail) of the system other parts of the solution might have. They just shouldn't care.
So, the first thing we want to do, is manage "bookings", so lets start there. You've established the basic requirements of a booking and we can describe it as such...
public interface Booking {
public String getName();
public String getPhoneNumber();
public String getAddress();
public String getDescription();
public LocalDate getDateOfBirth();
}
Now, we need some way for the other parts of the system to interact with this data, in away which decouples them from the underlying implementation, I'm mean, why would the booking view care how the bookings are stored, it just cares it can make them :/
So, lets add a PatientRepository
public interface PatientRepository {
public Booking createBooking(String name, String phoneNumber, String address, String description, LocalDate dob) throws IOException;
}
Ok, so, this "describes" parts of the systems, providing information about how some elements can be created, what we need next is some kind of implementation.
A booking is pretty basic, it's just a POJO, encapsulating the captured information...
public class DefaultBooking implements Booking {
private String name;
private String phoneNumber;
private String address;
private String description;
private LocalDate dateOfBirth;
public DefaultBooking(String name, String phoneNumber, String address, String description, LocalDate dateOfBirth) {
this.name = name;
this.phoneNumber = phoneNumber;
this.address = address;
this.description = description;
this.dateOfBirth = dateOfBirth;
}
public String getName() {
return name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getAddress() {
return address;
}
public String getDescription() {
return description;
}
public LocalDate getDateOfBirth() {
return dateOfBirth;
}
}
The PatientRepository
is a little more complicated.
Rolling your own file format is always a lot of work and I would consider things like JAXB/XML, JSON, CSV (using something like OpenCSV or Apache Commons CSV), but ultimately, your end goal should be to make use of some kind of database and make use of JDBC (possibly making use of something H2 or HSQL) (IMHO)
For this example, we're just rolling a simple line based file using ;
as a property delimiter (or separator)
public class DefaultPatientRepository implements PatientRepository {
private List<Booking> bookings;
public DefaultPatientRepository() throws IOException {
bookings = new ArrayList<>(32);
File sourceFile = new File("Clinic.txt");
if (sourceFile.exists()) {
try (BufferedReader br = new BufferedReader(new FileReader(sourceFile))) {
String line = null;
while ((line = br.readLine()) != null) {
String parts[] = line.split(";");
String name = parts[0];
String phoneNumber = parts[1];
String address = parts[2];
String description = parts[3];
String dob = parts[4];
LocalDate dateOfBirth = LocalDate.parse(dob, DateTimeFormatter.ISO_LOCAL_DATE);
Booking booking = new DefaultBooking(name, phoneNumber, address, description, dateOfBirth);
bookings.add(booking);
}
}
System.out.println("!! Loaded " + bookings.size() + " bookings");
}
}
@Override
public Booking createBooking(String name, String phoneNumber, String address, String description, LocalDate dob) throws IOException {
// Rolling your own file format is just a complete pain in the ... code
// To my mind, I'd explore XML, JSON and possibly even CSV if you're
// not ready for a SQL based database solution, but I'm old and I'm lazy :P
try (BufferedWriter bw = new BufferedWriter(new FileWriter(new File("Clinic.txt"), true))) {
StringJoiner joiner = new StringJoiner(";");
joiner.add(name);
joiner.add(phoneNumber);
joiner.add(address);
joiner.add(description);
joiner.add(DateTimeFormatter.ISO_LOCAL_DATE.format(dob));
bw.write(joiner.toString());
bw.newLine();
}
return new DefaultBooking(name, phoneNumber, address, description, dob);
}
}
So, without doing anything else, we now have a basic solution. You can test it and verify it's functionality without the needed to include any other parts of the system.
A word of warning though, this solution will store the Clinic.txt
in the current working directory, this can be problematic for a number of reasons.
- The working directory isn't static and is contextual to the location from which the program was launched
- The current work directory might be write protected (this happens way more often then you think)
For this, I would have a look at my answer for java- reading the properties file outside of jar file or Get current path of executed file which describes making use of "well known locations" based on the platform
And now, we've reached the point we can introduce the UI.
I strongly encourage you to have a look at A Visual Guide to Layout Managers and How to Use CardLayout
User interfaces are typically very complex and involve multiple different views and interactions. You want to spend you're time decoupling the views from each other and managing the scope of responsibility.
For example, just in your basic example, you have three distinct views.
- The menu
- The booking view
- The patient view (presumably involving some kind of search, which will add another view)
The menu doesn't care about the other two nor does it have an responsibility for managing them beyond showing them when the user requests them.
Equally, the other views don't care about the menu. All they really need to do is tell "some body" that they are no longer needed by the user.
The following example is an attempt to demonstrate a "possible" solution based around the use of CardLayout
, it makes use of such concepts as
- Observer pattern
- Dependency injection
- Decoupling of responsibilities
One particular feature you should pay attention to, is the ease at which it would be change the underlying implementation of the PatientRepository
(to use a different file format, local database or even a web service), as the implementation is not tightly coupled to the rest of the system
import java.awt.CardLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.EventObject;
import java.util.List;
import java.util.StringJoiner;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
PatientRepository patientRepository = new DefaultPatientRepository();
JFrame frame = new JFrame();
frame.add(new ClinicPane(patientRepository));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(null, "Failed to load patient repository", "Error", JOptionPane.ERROR_MESSAGE);
}
}
});
}
public class ClinicPane extends JPanel {
private CardLayout cardLayout;
private PatientRepository patientRepository;
private DismissablePaneListener dismissablePaneListener = new DismissablePaneListener() {
@Override
public void didDismissView(DismissablePaneEvent evt) {
// If you really wanted to you could consider having some kind
// of idenitifier for the view and then have some custom logic
// in place to determine which view to show next, but in this
// case, we just want to show the menu again
cardLayout.show(ClinicPane.this, "MainMenu");
}
};
public ClinicPane(PatientRepository patientRepository) {
this.patientRepository = patientRepository;
cardLayout = new CardLayout();
setLayout(cardLayout);
add(new MenuPane(new MenuListener() {
@Override
public void didSelectMenuOption(MenuEvent evt) {
switch (evt.getOption()) {
case MAKE_A_BOOKING:
cardLayout.show(ClinicPane.this, "Booking");
break;
case VIEW_PATIENT_RECORD:
cardLayout.show(ClinicPane.this, "Patient");
break;
}
}
}), "MainMenu");
BookingPane bookingPane = new BookingPane(patientRepository);
configure(bookingPane);
add(bookingPane, "Booking");
PatientPane patientPane = new PatientPane(patientRepository);
configure(patientPane);
add(patientPane, "Patient");
}
protected DismissablePaneListener getDismissablePaneListener() {
return dismissablePaneListener;
}
protected void configure(DismissablePane dismissablePane) {
dismissablePane.addDismissablePaneListener(getDismissablePaneListener());
}
public PatientRepository getPatientRepository() {
return patientRepository;
}
}
public enum MenuOption {
MAKE_A_BOOKING,
VIEW_PATIENT_RECORD,
}
public class MenuEvent extends EventObject {
private MenuOption option;
public MenuEvent(Object source, MenuOption option) {
super(source);
this.option = option;
}
public MenuOption getOption() {
return option;
}
}
public interface MenuListener extends EventListener {
public void didSelectMenuOption(MenuEvent evt);
}
public class MenuPane extends JPanel {
public MenuPane() {
this(null);
}
public MenuPane(MenuListener listener) {
if (listener != null) {
addMenuListener(listener);
}
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(8, 8, 8, 8);
add(new JLabel("Please select one Option"), gbc);
add(makeButtonFor(MenuOption.MAKE_A_BOOKING), gbc);
add(makeButtonFor(MenuOption.VIEW_PATIENT_RECORD), gbc);
}
public void addMenuListener(MenuListener listener) {
listenerList.add(MenuListener.class, listener);
}
public void removeMenuListener(MenuListener listener) {
listenerList.remove(MenuListener.class, listener);
}
protected JButton makeButtonFor(MenuOption action) {
JButton btn = new JButton("Unknown action");
switch (action) {
case MAKE_A_BOOKING:
btn.setText("Make a booking");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fireDidSelectMenuOption(action);
}
});
break;
case VIEW_PATIENT_RECORD:
btn.setText("View patient record");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fireDidSelectMenuOption(action);
}
});
break;
}
return btn;
}
protected void fireDidSelectMenuOption(MenuOption option) {
MenuListener[] listeners = listenerList.getListeners(MenuListener.class);
if (listeners.length == 0) {
return;
}
MenuEvent evt = new MenuEvent(this, option);
for (MenuListener listener : listeners) {
listener.didSelectMenuOption(evt);
}
}
}
public class DismissablePaneEvent extends EventObject {
public DismissablePaneEvent(DismissablePane source) {
super(source);
}
public DismissablePane getDismissablePane() {
return (DismissablePane) getSource();
}
}
public interface DismissablePaneListener extends EventListener {
public void didDismissView(DismissablePaneEvent evt);
}
public interface DismissablePane {
public JPanel getPane();
public void addDismissablePaneListener(DismissablePaneListener listener);
public void removeDismissablePaneListener(DismissablePaneListener listener);
}
public abstract class AbstractDismissablePane extends JPanel implements DismissablePane {
@Override
public JPanel getPane() {
return this;
}
@Override
public void addDismissablePaneListener(DismissablePaneListener listener) {
listenerList.add(DismissablePaneListener.class, listener);
}
@Override
public void removeDismissablePaneListener(DismissablePaneListener listener) {
listenerList.remove(DismissablePaneListener.class, listener);
}
protected void fireDidDismissPane() {
DismissablePaneListener[] listeners = listenerList.getListeners(DismissablePaneListener.class);
if (listeners.length == 0) {
return;
}
DismissablePaneEvent evt = new DismissablePaneEvent(this);
for (DismissablePaneListener listener : listeners) {
listener.didDismissView(evt);
}
}
}
public class BookingPane extends AbstractDismissablePane {
private PatientRepository patientRepository;
public BookingPane(PatientRepository patientRepository) {
this.patientRepository = patientRepository;
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(8, 8, 8, 8);
add(new JLabel("Please make a booking"), gbc);
add(new JLabel("This is where you'd collect all the booking details"), gbc);
JButton save = new JButton("Save");
JButton cancel = new JButton("Cancel");
save.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
// Create an instance of Booking
Booking booking = getPatientRepository().createBooking("name", "phone number", "address", "description", LocalDate.now());
// Dismiss the view
fireDidDismissPane();
} catch (IOException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(BookingPane.this, "Failed to create booking", "Error", JOptionPane.ERROR_MESSAGE);
}
}
});
cancel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fireDidDismissPane();
}
});
JPanel buttonPane = new JPanel(new GridLayout(1, 2));
buttonPane.add(cancel);
buttonPane.add(save);
add(buttonPane, gbc);
}
public PatientRepository getPatientRepository() {
return patientRepository;
}
}
public class PatientPane extends AbstractDismissablePane {
private PatientRepository patientRepository;
public PatientPane(PatientRepository patientRepository) {
this.patientRepository = patientRepository;
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(8, 8, 8, 8);
add(new JLabel("Patient Record"), gbc);
add(new JLabel("All your record belong to us"), gbc);
JButton okay = new JButton("Okay");
okay.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fireDidDismissPane();
}
});
add(okay, gbc);
}
public PatientRepository getPatientRepository() {
return patientRepository;
}
}
public interface Booking {
public String getName();
public String getPhoneNumber();
public String getAddress();
public String getDescription();
public LocalDate getDateOfBirth();
}
public interface PatientRepository {
public Booking createBooking(String name, String phoneNumber, String address, String description, LocalDate dob) throws IOException;
}
public class DefaultBooking implements Booking {
private String name;
private String phoneNumber;
private String address;
private String description;
private LocalDate dateOfBirth;
public DefaultBooking(String name, String phoneNumber, String address, String description, LocalDate dateOfBirth) {
this.name = name;
this.phoneNumber = phoneNumber;
this.address = address;
this.description = description;
this.dateOfBirth = dateOfBirth;
}
public String getName() {
return name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getAddress() {
return address;
}
public String getDescription() {
return description;
}
public LocalDate getDateOfBirth() {
return dateOfBirth;
}
}
public class DefaultPatientRepository implements PatientRepository {
private List<Booking> bookings;
public DefaultPatientRepository() throws IOException {
bookings = new ArrayList<>(32);
File sourceFile = new File("Clinic.txt");
if (sourceFile.exists()) {
try (BufferedReader br = new BufferedReader(new FileReader(sourceFile))) {
String line = null;
while ((line = br.readLine()) != null) {
String parts[] = line.split(";");
String name = parts[0];
String phoneNumber = parts[1];
String address = parts[2];
String description = parts[3];
String dob = parts[4];
LocalDate dateOfBirth = LocalDate.parse(dob, DateTimeFormatter.ISO_LOCAL_DATE);
Booking booking = new DefaultBooking(name, phoneNumber, address, description, dateOfBirth);
bookings.add(booking);
}
}
System.out.println("!! Loaded " + bookings.size() + " bookings");
}
}
@Override
public Booking createBooking(String name, String phoneNumber, String address, String description, LocalDate dob) throws IOException {
// Rolling your own file format is just a complete pain in the ... code
// To my mind, I'd explore XML, JSON and possibly even CSV if you're
// not ready for a SQL based database solution, but I'm old and I'm lazy :P
try (BufferedWriter bw = new BufferedWriter(new FileWriter(new File("Clinic.txt"), true))) {
StringJoiner joiner = new StringJoiner(";");
joiner.add(name);
joiner.add(phoneNumber);
joiner.add(address);
joiner.add(description);
joiner.add(DateTimeFormatter.ISO_LOCAL_DATE.format(dob));
bw.write(joiner.toString());
bw.newLine();
}
return new DefaultBooking(name, phoneNumber, address, description, dob);
}
}
}
I bet you're now sorry you even asked ;)