Simplify the RocketComponentVisitor by removing the generic interfaces.

Added functionality to the RocketComponentVisitor by providing a
templated return value.

Modified the Configuration tab UI to disable recovery & staging tabs
when they are not appropriate and adding some help text to the motor
table when there are no motor mounts defined.
This commit is contained in:
kruland2607 2013-12-19 10:40:11 -06:00
parent f334fa1ec1
commit 305ad36103
12 changed files with 168 additions and 117 deletions

View File

@ -57,7 +57,7 @@ RocketPanel.lbl.ViewType = View Type:
! BasicFrame
BasicFrame.tab.Rocketdesign = Rocket design
BasicFrame.tab.Flightconfig = Configurations
BasicFrame.tab.Flightconfig = Motors & Configuration
BasicFrame.tab.Flightsim = Flight simulations
BasicFrame.title.Addnewcomp = Add new component
BasicFrame.dlg.lbl1 = Design '
@ -1866,6 +1866,7 @@ MotorConfigurationPanel.btn.removeMotor = Remove motor
MotorConfigurationPanel.btn.selectMotor = Select motor
MotorConfigurationPanel.btn.selectIgnition = Select ignition
MotorConfigurationPanel.btn.resetIgnition = Reset ignition
MotorConfigurationPanel.lbl.nomotors = No motor mounts defined. Select one or more tubes from the list on the left as motor mounts.
MotorConfigurationTableModel.table.ignition.default = Default ({0})
RecoveryConfigurationPanel.table.deployment.default = Default ({0})

View File

@ -28,7 +28,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent>, Visitable<RocketComponentVisitor, RocketComponent> {
public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent> {
private static final Logger log = LoggerFactory.getLogger(RocketComponent.class);
// Because of changes to Java 1.7.0-45's mechanism to construct DataFlavor objects (used in Drag and Drop)
@ -1736,9 +1736,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
}
///////////// Visitor pattern implementation
@Override
public void accept(RocketComponentVisitor visitor) {
public <R> R accept(RocketComponentVisitor<R> visitor) {
visitor.visit(this);
return visitor.getResult();
}
//////////// Helper methods for subclasses

View File

@ -1,5 +1,19 @@
package net.sf.openrocket.rocketcomponent;
public interface RocketComponentVisitor extends Visitor<RocketComponentVisitor, RocketComponent> {
public interface RocketComponentVisitor<R> {
/**
* The callback method. This method is the 2nd leg of the double-dispatch, having been invoked from a
* corresponding <code>accept</code>.
*
* @param visitable the instance of the Visitable (the target of what is being visiting)
*/
void visit(RocketComponent visitable);
/**
* Return the final result
* @return
*/
R getResult();
}

View File

@ -1,41 +0,0 @@
/*
* Visitable.java
*/
package net.sf.openrocket.rocketcomponent;
/**
* This interface describes a portion of the Visitor pattern, using generics to assure type-safety.
* The elements of the concrete object hierarchy are only visitable by an associated hierarchy of visitors,
* while these visitors are only able to visit the elements of that hierarchy.
*
* The key concept regarding the Visitor pattern is to realize that Java will only "discriminate" the type of an
* object being called, not the type of an object being passed.
*
* In order for the type of two objects to be determinable to the JVM, each object must be the receiver of an
* invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes "known" but the
* concrete type of the argument is still unknown. <code>visit</code> is then called on the parameter object, passing
* the Visitable back, which has type and identity. Flow of control has now been 'double-dispatched' such that the
* type (and identity) of both objects are known.
*
* Specifically, this interface is to be implemented by every class in the RocketComponent hierarchy that
* can be visited AND which are sufficiently specialized from their super class. If they only provide
* constraints to their superclass (such as TubeCoupler), then the implementation of this interface at
* the superclass level is sufficient.
*
* Admittedly, the syntax is a bit contorted here, as it is necessarily self-referential for type-safety.
*
* <V> The visitor type
* <T> The visitable (the concrete class that implements this interface)
*/
public interface Visitable<V extends Visitor<V, T>, T extends Visitable<V, T>> {
/**
* Any class in the hierarchy that allows itself to be visited will implement this method. The normal
* behavior is that the visitor will invoke this method of a Visitable, passing itself. The Visitable
* turns around calls the Visitor back. This idiom is also known as 'double-dispatching'.
*
* @param visitor the visitor that will be called back
*/
public void accept(V visitor);
}

View File

@ -1,39 +0,0 @@
/*
* Visitor.java
*/
package net.sf.openrocket.rocketcomponent;
/**
* This interface describes a portion of the Visitor pattern, using generics to assure type-safety.
* The elements of the concrete object hierarchy are only visitable by an associated hierarchy of visitors,
* while these visitors are only able to visit the elements of that hierarchy.
*
* The key concept regarding the Visitor pattern is to realize that Java will only "discriminate" the type of an
* object being called, not the type of an object being passed.
*
* In order for the type of two objects to be determinable to the JVM, each object must be the receiver of an
* invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes "known" but the
* concrete type of the argument is still unknown. <code>visit</code> is then called on the parameter object, passing
* the Visitable back, which has type and identity. Flow of control has now been 'double-dispatched' such that the
* type (and identity) of both objects are known.
*
* Specifically, this interface is to be implemented by every class in the RocketComponent hierarchy that
* can be visited AND which are sufficiently specialized from their super class. If they only provide
* constraints to their superclass (such as TubeCoupler), then the implementation of this interface at
* the superclass level is sufficient.
*
* Admittedly, the syntax is a bit contorted here, as it is necessarily self-referential for type-safety.
*
* <V> The visitor type (the concrete class that implements this interface)
* <T> The visitable
*/
public interface Visitor<V extends Visitor<V, T>, T extends Visitable<V, T>> {
/**
* The callback method. This method is the 2nd leg of the double-dispatch, having been invoked from a
* corresponding <code>accept</code>.
*
* @param visitable the instance of the Visitable (the target of what is being visiting)
*/
void visit(T visitable);
}

View File

@ -3,24 +3,24 @@ package net.sf.openrocket.rocketvisitors;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.RocketComponentVisitor;
public abstract class BredthFirstRecusiveVisitor implements RocketComponentVisitor {
public abstract class BredthFirstRecusiveVisitor<R> implements RocketComponentVisitor<R> {
@Override
public final void visit(RocketComponent visitable) {
this.doAction(visitable);
for ( RocketComponent child: visitable.getChildren() ) {
for (RocketComponent child : visitable.getChildren()) {
this.doAction(child);
}
for ( RocketComponent child: visitable.getChildren() ) {
for (RocketComponent child : visitable.getChildren()) {
this.visit(child);
}
}
protected abstract void doAction( RocketComponent visitable );
protected abstract void doAction(RocketComponent visitable);
}

View File

@ -1,9 +1,9 @@
package net.sf.openrocket.rocketvisitors;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent;
import net.sf.openrocket.rocketcomponent.RocketComponent;
public class CopyFlightConfigurationVisitor extends DepthFirstRecusiveVisitor {
public class CopyFlightConfigurationVisitor extends DepthFirstRecusiveVisitor<Void> {
private final String oldConfigId;
private final String newConfigId;
@ -17,10 +17,14 @@ public class CopyFlightConfigurationVisitor extends DepthFirstRecusiveVisitor {
@Override
public void doAction(RocketComponent visitable) {
if ( visitable instanceof FlightConfigurableComponent ) {
((FlightConfigurableComponent)visitable).cloneFlightConfiguration(oldConfigId, newConfigId);
if (visitable instanceof FlightConfigurableComponent) {
((FlightConfigurableComponent) visitable).cloneFlightConfiguration(oldConfigId, newConfigId);
}
}
@Override
public Void getResult() {
return null;
}
}

View File

@ -3,19 +3,19 @@ package net.sf.openrocket.rocketvisitors;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.RocketComponentVisitor;
public abstract class DepthFirstRecusiveVisitor implements RocketComponentVisitor {
public abstract class DepthFirstRecusiveVisitor<R> implements RocketComponentVisitor<R> {
@Override
public final void visit(RocketComponent visitable) {
this.doAction(visitable);
for ( RocketComponent child: visitable.getChildren() ) {
for (RocketComponent child : visitable.getChildren()) {
this.visit(child);
}
}
protected abstract void doAction( RocketComponent visitable );
protected abstract void doAction(RocketComponent visitable);
}

View File

@ -0,0 +1,30 @@
package net.sf.openrocket.rocketvisitors;
import java.util.ArrayList;
import java.util.List;
import net.sf.openrocket.rocketcomponent.RocketComponent;
public class ListComponents<T extends RocketComponent> extends DepthFirstRecusiveVisitor<List<T>> {
private final Class<T> componentClazz;
protected List<T> components = new ArrayList<T>();
public ListComponents(Class<T> componentClazz) {
super();
this.componentClazz = componentClazz;
}
@Override
public List<T> getResult() {
return components;
}
@Override
protected void doAction(RocketComponent visitable) {
if (componentClazz.isAssignableFrom(visitable.getClass())) {
components.add((T) visitable);
}
}
}

View File

@ -0,0 +1,18 @@
package net.sf.openrocket.rocketvisitors;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RocketComponent;
public class ListMotorMounts extends ListComponents<RocketComponent> {
public ListMotorMounts() {
super(RocketComponent.class);
}
@Override
protected void doAction(RocketComponent visitable) {
if (visitable instanceof MotorMount && ((MotorMount) visitable).isMotorMount()) {
components.add(visitable);
}
}
}

View File

@ -16,8 +16,11 @@ import net.sf.openrocket.gui.dialogs.flightconfiguration.RenameConfigDialog;
import net.sf.openrocket.gui.main.BasicFrame;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketvisitors.ListComponents;
import net.sf.openrocket.rocketvisitors.ListMotorMounts;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.StateChangeListener;
@ -39,6 +42,10 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe
private final RecoveryConfigurationPanel recoveryConfigurationPanel;
private final SeparationConfigurationPanel separationConfigurationPanel;
private final static int MOTOR_TAB_INDEX = 0;
private final static int RECOVERY_TAB_INDEX = 1;
private final static int SEPARATION_TAB_INDEX = 2;
@Override
public void stateChanged(EventObject e) {
updateButtonState();
@ -178,10 +185,38 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe
private void updateButtonState() {
String currentId = rocket.getDefaultConfiguration().getFlightConfigurationID();
// Enable the remove/rename/copy buttons only when a configuration is selected.
removeConfButton.setEnabled(currentId != null);
renameConfButton.setEnabled(currentId != null);
copyConfButton.setEnabled(currentId != null);
// Count the number of motor mounts
int motorMountCount = rocket.accept(new ListMotorMounts()).size();
// Count the number of recovery devices
int recoveryDeviceCount = rocket.accept(new ListComponents<RecoveryDevice>(RecoveryDevice.class)).size();
// Count the number of stages
int stageCount = rocket.getStageCount();
// Enable the new configuration button only when a motor mount is defined.
newConfButton.setEnabled(motorMountCount > 0);
// Only enable the recovery tab if there is a motor mount and there is a recovery device
tabs.setEnabledAt(RECOVERY_TAB_INDEX, motorMountCount > 0 && recoveryDeviceCount > 0);
// If the selected tab was the recovery tab, and there is no longer any recovery devices,
// switch to the motor tab.
if( recoveryDeviceCount == 0 && tabs.getSelectedIndex() == RECOVERY_TAB_INDEX ) {
tabs.setSelectedIndex(MOTOR_TAB_INDEX);
}
tabs.setEnabledAt(SEPARATION_TAB_INDEX, motorMountCount > 0 && stageCount > 1);
if ( stageCount ==1 && tabs.getSelectedIndex() == SEPARATION_TAB_INDEX ) {
tabs.setSelectedIndex(MOTOR_TAB_INDEX);
}
}
}

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.gui.main.flightconfigpanel;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
@ -49,6 +50,10 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
private final JButton selectMotorButton, removeMotorButton, selectIgnitionButton, resetIgnitionButton;
private final JPanel cards;
private final static String HELP_LABEL = "help";
private final static String TABLE_LABEL = "table";
private final MotorChooserDialog motorChooserDialog;
protected FlightConfigurableTableModel<MotorMount> configurationTableModel;
@ -57,7 +62,6 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
motorChooserDialog = new MotorChooserDialog(SwingUtilities.getWindowAncestor(flightConfigurationPanel));
{
//// Select motor mounts
JPanel subpanel = new JPanel(new MigLayout(""));
@ -75,8 +79,16 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
this.add(subpanel, "split, w 200lp, growy");
}
cards = new JPanel(new CardLayout());
this.add( cards );
JLabel helpText = new JLabel(trans.get("MotorConfigurationPanel.lbl.nomotors"));
cards.add(helpText, HELP_LABEL );
JScrollPane scroll = new JScrollPane(table);
this.add(scroll, "grow, wrap");
cards.add(scroll, TABLE_LABEL );
this.add(cards, "grow, wrap");
//// Select motor
selectMotorButton = new JButton(trans.get("MotorConfigurationPanel.btn.selectMotor"));
@ -122,6 +134,14 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
}
protected void showEmptyText() {
((CardLayout)cards.getLayout()).show(cards, HELP_LABEL);
}
protected void showContent() {
((CardLayout)cards.getLayout()).show(cards, TABLE_LABEL);
}
@Override
protected JTable initializeTable() {
//// Motor selection table.
@ -165,12 +185,21 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
}
private void updateButtonState() {
if( configurationTableModel.getColumnCount() > 1 ) {
showContent();
String currentID = rocket.getDefaultConfiguration().getFlightConfigurationID();
MotorMount currentMount = getSelectedComponent();
selectMotorButton.setEnabled(currentMount != null && currentID != null);
removeMotorButton.setEnabled(currentMount != null && currentID != null);
selectIgnitionButton.setEnabled(currentMount != null && currentID != null);
resetIgnitionButton.setEnabled(currentMount != null && currentID != null);
} else {
showEmptyText();
selectMotorButton.setEnabled(false);
removeMotorButton.setEnabled(false);
selectIgnitionButton.setEnabled(false);
resetIgnitionButton.setEnabled(false);
}
}