I am writing an application with Java 8 using NetBeans, and want some reassurance that I am going the correct way with this.
I am finding it tough because of the use of Swing.
I have gone for one model (for now), one controller and a set of views. All of the MVC examples for Java/Swing involve one JDialog, one model and one controller.
The controller maintains a list of views and commands, each of which are in a HashMap and are initiated in the main
method of the application.
Each command takes the dependencies required to carry out the logic between the view and model in it's constructor. I know this is not the strict implementation of the Command pattern, but I can not think of how to get the two talking to each other without violating the Open/Closed principle.
Also, because of the nature of each View, in each command, I have made reference to the concrete version of the view instead of the abstract version -- this is for updating various fields in each view.
Please criticize my code... I am new to this, and need all the help I can get.
Below is what I have at the moment:
TopLevelController.java:
public class TopLevelController implements ActionListener {
private final Map<String,View> views = new HashMap<>();
private final Map<String,Command> commands = new HashMap<>();
private VFrontOfHouse frontOfHouse;
private Model model;
public void initiate(VFrontOfHouse frontOfHouse) {
// Have a reference to the top level container (Front of house)
this.frontOfHouse = frontOfHouse;
// Iterate through the views, and initiate them with this controller as a listener
Set<String> keys = views.keySet();
for(String viewKey : keys) {
views.get(viewKey).setListeners(this);
System.out.println(viewKey);
}
}
public void setModel(Model model) {
this.model = model;
}
public void addView(String viewKey, View view) {
views.put(viewKey, view);
}
public View getView(String viewKey) {
View view = null;
try{
view = views.get(viewKey);
}
catch(NullPointerException ex) {
JOptionPane.showMessageDialog(frontOfHouse, "No such view: "+viewKey);
}
return view;
}
public void addCommand(String cmdKey, Command cmd) {
commands.put(cmdKey, cmd);
}
public void executeCommand(String cmdKey) {
try {
commands.get(cmdKey).execute();
}
catch (NullPointerException npe) {
JOptionPane.showMessageDialog(null, "Unknown command "+cmdKey);
}
}
@Override
public void actionPerformed(ActionEvent e) {
executeCommand(e.getActionCommand());
}
}
Model.java
public class Model {
private String result;
private ClientSession session;
private QueryRunner runner;
public void createSession(BaseXCredentials bc) throws IOException {
session = new ClientSession(bc.getHost(),bc.getPort(),bc.getUid(),bc.getPassword());
runner = new QueryRunner(session);
}
public String runQuery(String query) throws IOException {
result = runner.runQuery(query);
return result;
}
public void importVP(String path, String dbName) throws IOException {
// Create a new database
runner.createDB(dbName);
String query = "db:add(\""+dbName+"\",\""+ path +"\")";
runner.runQuery(query);
runner.close(dbName);
runner.openDB("VPData");
runner.runQuery("insert node <db>"+dbName+"</db> into ./databases");
runner.close("VPData");
}
public String[] getDBList() throws IOException {
ArrayList<Database> databases = new ArrayList<>();
runner.openDB("VPData");
result = runner.runQuery("databases");
XMLReaderSimpleList reader = new XMLReaderSimpleList(databases);
reader.read(result);
String[] strings;
Integer i;
strings = new String[databases.size()];
for(i = 0; i < databases.size(); i++) {
strings[i] = databases.get(i).getDatabase();
}
return strings;
}
public void openDB(String dbString) throws IOException {
runner.openDB(dbString);
}
public String getCurrentDb() {
return runner.getCurrentDB();
}
}
View.java
public abstract class View {
/**
* void initComponents() -- Sets up the components
* T readFields() -- reads the fields into an object of type T
*/
protected final JDialog dialog;
protected final String dialogName;
public View(String dialogName) {
this.dialogName = dialogName;
this.dialog = new JDialog();
this.initComponents();
}
public abstract<T> T readFields();
protected abstract void initComponents();
public abstract void setListeners(ActionListener listener);
public JDialog getDialog() {return dialog;}
public void showForm() {getDialog().setVisible(true);}
public void hideForm() {getDialog().setVisible(
}
A concrete view (VBaseXLogin.java)
public class VBasexLogin extends UpdatableView {
private ActionListener listener;
private JButton connectButton;
private JLabel connectionLabel;
private JTextField hostField;
private JLabel hostLabel;
private JLabel passwordLabel;
private JButton pingButton;
private JTextField portField;
private JLabel portLabel;
private JPasswordField pwField;
private JLabel titleLabel;
private JTextField uidField;
private JLabel userId;
public VBasexLogin(String dialogString) {
super(dialogString);
}
public void initComponents() {
Boilerplate code for view
}
@Override
public void setListeners(ActionListener listener) {
pingButton.setActionCommand("PING");
connectButton.setActionCommand("CONNECT");
pingButton.addActionListener(listener);
connectButton.addActionListener(listener);
}
/**
*
* @return
*/
@Override
public BaseXCredentials readFields() {
return new BaseXCredentials(hostField.getText(),portField.getText(),uidField.getText(),pwField.getPassword());
}
@Override
public void updateFields(String[] updates) {
connectionLabel.setText(updates[0]);
}
Command.java
public interface Command {
public void execute();
}
A concrete command (BaseXLogin.java)
public class BaseXLogin implements Command {
private final View basexLogin;
private final Model model;
private final VFrontOfHouse foh;
public BaseXLogin(View basexLogin, VFrontOfHouse foh, Model model) {
this.basexLogin = basexLogin;
this.model = model;
this.foh = foh;
}
@Override
public void execute() {
try {
BaseXCredentials credentials = basexLogin.readFields();
model.createSession(credentials);
basexLogin.hideForm();
foh.enableQueryRunner();
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, "I/O Exception: "+ex.getMessage());
}
}
}
The 'main' app tying it all together:
package consolemvc;
import Commands.*;
import Entities.View;
import consolemvc.views.refactor.VBasexLogin;
import consolemvc.views.refactor.VFileChooser;
import consolemvc.views.refactor.VFrontOfHouse;
import consolemvc.views.refactor.VImportVP;
import consolemvc.views.refactor.VQueryRunner;
import consolemvc.views.refactor.VVPDataBaseSelector;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ConsoleMVC {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
try {
// Set System L&F
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
}
catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
// handle exception
}
// Create the controller
final TopLevelController controller = new TopLevelController();
// Create the front of house view
final VFrontOfHouse frontOfHouse = new VFrontOfHouse();
// Create the model
Model model = new Model();
// Create the views
View login = new VBasexLogin("BAESXLOGIN");
View queryRunner = new VQueryRunner("XQUERYRUNNER");
View fileChooser = new VFileChooser("FILECHOOSEROPEN");
View vpImport = new VImportVP("VPIMPORT");
View dbSelector = new VVPDataBaseSelector("VPDBSELECTOR");
// Set the views up in the controller
controller.addView("BASEXLOGIN", login);
controller.addView("XQUERYRUNNER", queryRunner);
controller.addView("FILECHOOSEROPEN", fileChooser);
controller.addView("VPIMPORT", vpImport);
controller.addView("VPDBSELECTOR", dbSelector);
// Set the commands
// The Database selector
controller.addCommand("SHOWVPDBSELECTOR", new ShowDBSelector((VVPDataBaseSelector) controller.getView("VPDBSELECTOR"),model));
controller.addCommand("SETVPDB", new SetVPDatabase((VVPDataBaseSelector) controller.getView("VPDBSELECTOR"),model));
// Basex Login screen
controller.addCommand("SHOWBASEXLOGIN", new ShowBaseXLogin((VBasexLogin) controller.getView("BASEXLOGIN")));
controller.addCommand("CONNECT",new BaseXLogin(controller.getView("BASEXLOGIN"), frontOfHouse, model));
controller.addCommand("PING",new BaseXLogin(controller.getView("BASEXLOGIN"), frontOfHouse, model));
// The VP Import Data screen
controller.addCommand("SHOWVPIMPORT", new ShowVPImport((VImportVP) controller.getView("VPIMPORT")));
controller.addCommand("SHOWFILECHOOSER", new ShowVPImport((VImportVP) controller.getView("VPIMPORT")));
controller.addCommand("IMPORT", new ImportVPData((VFileChooser) controller.getView("FILECHOOSEROPEN"),
(VImportVP) controller.getView("VPIMPORT"),
model));
// The XQuery Runner
controller.addCommand("SHOWXQUERYRUNNER", new ShowQueryRunner((VQueryRunner) controller.getView("XQUERYRUNNER"), model));
controller.addCommand("RUNXQUERY", new RunQuery((VQueryRunner) controller.getView("XQUERYRUNNER"),model));
// Set the model
controller.setModel(model);
// Link controllers with view, and the frontOgHouse
controller.initiate(frontOfHouse);
// And fire up the frontofhouse in the EDT
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
frontOfHouse.addListeners(controller);
frontOfHouse.setVisible(true);
}
});
}
}