3
\$\begingroup\$

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);
    }
    });
}
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Just a few comments, not really relevant to your main concerns. Swing is strange, and I myself get often lost in its logic.


try{
    view = views.get(viewKey);
}
catch(NullPointerException ex) {
    JOptionPane.showMessageDialog(frontOfHouse, "No such view: "+viewKey);
}

There's no NPE thrown here. You simply get and return null. Anyway, catching exception instead of proper checks is wrong. It should read

public View getView(String viewKey) {
    View view = views.get(viewKey);
    if (view==null) {
        JOptionPane.showMessageDialog(frontOfHouse, "No such view: "+viewKey);
    }       
    return view;
}

And it's quite possible that this is not the right place for showing any messages.


// TODO code application logic here

Is there something missing?

// handle exception

Do it.... in this case some logging should be enough. Or maybe a comment like // ignored as setLookAndFeel is rather unimportant.

// Set System L&F
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

A nice example of an anti-comment stating the obvious. Do you know anyone who could be helper by this???

// Create the controller
final TopLevelController controller = new TopLevelController();

Again.

// Create the front of house view
final VFrontOfHouse frontOfHouse = new VFrontOfHouse();

And again.

// Create the model
 Model model = new Model();

And again.


Your main is pretty long. I always try to keep it at <5 lines as the static context is too bad. Create a helper class containing

private final TopLevelController controller = new TopLevelController();
private final VFrontOfHouse frontOfHouse = new VFrontOfHouse();
private final Model model = new Model();

I must admit that all the Swing MVC and command pattern don't work for me well. Instead, I prefer putting the application together using Guice and EventBus (my listeners just post the event to an object which gets everything it needs in its constructor).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.