Give trick-dre the capability to expand arrayed variables like trick-tv. #381

This commit is contained in:
Penn, John M 047828115 2021-04-29 14:29:18 -05:00
parent 437a56b24c
commit edcacba00f
2 changed files with 517 additions and 85 deletions

View File

@ -0,0 +1,88 @@
package trick.dre;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import org.jdesktop.swingx.JXLabel;
import org.jdesktop.swingx.JXPanel;
/**
* two combo boxes separated by a dash on a panel
*
* @author Derek Bankieris
*/
// Suppress warning about instatiating a generic type in Java 7.
@SuppressWarnings("unchecked")
public class DoubleComboBox extends JXPanel {
/** the first combo box */
JComboBox minimumBox = new JComboBox();
/** the second combo box */
JComboBox maximumBox = new JComboBox();
/**
* constructor
*
* @param count the maximum selectable value
*/
public DoubleComboBox(int count) {
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
for (int i = 0; i < count; i++) {
minimumBox.addItem(i);
maximumBox.addItem(i);
}
minimumBox.setSelectedIndex(0);
maximumBox.setSelectedIndex(count - 1);
minimumBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
int min = (int)minimumBox.getSelectedItem();
for (int i = ((Integer)maximumBox.getItemAt(0)); i < min; ++i) {
maximumBox.removeItemAt(0);
}
for (int i = ((Integer)maximumBox.getItemAt(0)); i > min; ) {
maximumBox.insertItemAt(--i, 0);
}
}
});
maximumBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
int max = (int)maximumBox.getSelectedItem();
for (int i = ((Integer)minimumBox.getItemAt(minimumBox.getItemCount() - 1)); i > max; --i) {
minimumBox.removeItemAt(minimumBox.getItemCount() - 1);
}
for (int i = ((Integer)minimumBox.getItemAt(minimumBox.getItemCount() - 1)); i < max; ) {
minimumBox.addItem(++i);
}
}
});
add(minimumBox);
add(new JXLabel() {{
setText("-");
}});
add(maximumBox);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
minimumBox.setEnabled(enabled);
maximumBox.setEnabled(enabled);
}
@Override
public String toString() {
return minimumBox.getSelectedItem() + "-" + maximumBox.getSelectedItem();
}
}

View File

@ -19,22 +19,30 @@ import trick.common.ui.UIUtils;
import trick.common.ui.components.NumberTextField;
import trick.common.ui.panels.ListPanel;
import trick.sie.utils.*;
import javax.swing.*;
import javax.swing.tree.TreePath;
import org.jdesktop.swingx.JXButton;
import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.JXTextField;
import javax.xml.parsers.ParserConfigurationException;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.List;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import trick.common.utils.vs.Variable;
/**
* Dre - data recording editor application.
@ -75,7 +83,6 @@ public class DreApplication extends TrickApplication {
private JPopupMenu treePopup = null;
/** S_sie.resource xml parser */
//private SieResourceXMLParser sieXMLParser;
/**
* The sim objects top level instances
@ -87,6 +94,11 @@ public class DreApplication extends TrickApplication {
*/
private SieVariableTree varTree;
/**
* Dialog for adding arrayed variables
*/
private AddVariableDialog addVariableDialog;
/**
* The search panel for the variable tree
*/
@ -123,7 +135,6 @@ public class DreApplication extends TrickApplication {
private JRadioButtonMenuItem DRBuffer_item;
private JRadioButtonMenuItem DRNoBuffer_item;
private JRadioButtonMenuItem DRRingBuffer_item;
//private JRadioButtonMenuItem DRThreadBuffer_item;
private JRadioButtonMenuItem DRAlways_item;
private JRadioButtonMenuItem DRChanges_item;
@ -135,8 +146,6 @@ public class DreApplication extends TrickApplication {
* Vectors to contain the information on the variable
* being added to the recording list.
*/
Vector<VariableName> nameSegment = new Vector<VariableName>();
Vector<String> fullName = new Vector<String>();
//========================================
// Constructors
@ -234,7 +243,6 @@ public class DreApplication extends TrickApplication {
DRBuffer_item.setSelected(true);
DRNoBuffer_item.setSelected(false);
DRRingBuffer_item.setSelected(false);
//DRThreadBuffer_item.setSelected(false);
}
@Action
@ -243,7 +251,6 @@ public class DreApplication extends TrickApplication {
DRBuffer_item.setSelected(false);
DRNoBuffer_item.setSelected(true);
DRRingBuffer_item.setSelected(false);
//DRThreadBuffer_item.setSelected(false);
}
@Action
@ -252,7 +259,6 @@ public class DreApplication extends TrickApplication {
DRBuffer_item.setSelected(false);
DRNoBuffer_item.setSelected(false);
DRRingBuffer_item.setSelected(true);
//DRThreadBuffer_item.setSelected(false);
}
@Action
@ -261,7 +267,6 @@ public class DreApplication extends TrickApplication {
DRBuffer_item.setSelected(false);
DRNoBuffer_item.setSelected(false);
DRRingBuffer_item.setSelected(false);
//DRThreadBuffer_item.setSelected(true);
}
@Action
@ -319,7 +324,6 @@ public class DreApplication extends TrickApplication {
} else {
resourceFile = new File(sieResourcePath);
}
//sieXMLParser = null;
if (!resourceFile.exists()) {
System.out.println(resourceFile.getName() + " file does not exist. Exit!!!");
System.exit(0);
@ -331,14 +335,6 @@ public class DreApplication extends TrickApplication {
e.printStackTrace();
}
/*try {
sieXMLParser = new SieResourceXMLParser(resourceFile.getCanonicalPath());
sieXMLParser.runParser();
} catch (Exception e) {
System.out.println("Failed to parse " + resourceFile.getName() + "!");
System.exit(0);
}*/
treePopup = new JPopupMenu();
varTree = new SieVariableTree();
@ -358,6 +354,8 @@ public class DreApplication extends TrickApplication {
protected void startup() {
super.startup();
addVariableDialog = new AddVariableDialog();
View view = getMainView();
view.setComponent(createMainPanel());
view.setMenuBar(createMenuBar());
@ -489,11 +487,7 @@ public class DreApplication extends TrickApplication {
optionsMenu.add(DRRingBuffer_item);
DRRingBuffer_item.setAction(getAction("selectDRRingBuffer"));
//DRThreadBuffer_item = new JRadioButtonMenuItem("selectDRThreadBuffer");
//optionsMenu.add(DRThreadBuffer_item);
//DRThreadBuffer_item.setAction(getAction("selectDRThreadBuffer"));
selectDRBuffer(); // by default, DR_Buffer
selectDRBuffer();
return optionsMenu;
}
@ -799,81 +793,424 @@ public class DreApplication extends TrickApplication {
}
/**
* routine to add the subscripts to the variable name being created
*
* @param index int the size of the segments array.
* Add the given variable name to DreApplication::variables.
*/
private void addSubscript(int index) {
if (nameSegment.get(index).dimensions.size() != 0) {
for (int j = 0; j < nameSegment.get(index).dimensions.size(); j++) {
int total = fullName.size();
for (int ii = 0; ii < total; ii++) {
String temp = fullName.remove(0);
for (int jj = 0; jj < nameSegment.get(index).dimensions.get(j); jj++) {
fullName.add(temp + "[" + jj + "]");
}
if (nameSegment.get(index).dimensions.get(j) == 0) {
fullName.add(temp + "[0]");
}
}
}
private void addVariable(String variable_name) {
variables.add(variable_name);
selectedVarList.addData(variable_name);
}
/**
* Add the given list of variable names to DreApplication::variables.
*/
protected void addVariables(List<String> variable_names) {
for (String name : variable_names) {
addVariable(name);
}
}
/**
* routine to add the segment name to the variable name being created.
*
* @param name String the segment name of the variable to be data recorded.
* Expand index ranges in an arrayed variable to a list of individual array elements.
*/
private void addName(String name) {
int total = fullName.size();
for (int ii = 0; ii < total; ii++) {
String temp = fullName.remove(0);
fullName.add(temp + "." + name);
}
}
protected List<String> getVariables(String variable_name) throws InvalidVariableNameException {
validateVariable(variable_name);
/* Remove all white space in the variable_name string. */
variable_name = variable_name.replaceAll("\\s", "");
/* Split the variable_name into segments, delimited by periods. */
String[] segments = variable_name.split("\\.");
/* The root of the variable name is the first segment minus
brackets, and anything in between them. */
String variable_name_root = segments[0].replaceFirst("\\[.*\\]", "");
/* Get the extents of each dimension in the first segment. */
ArrayList<String> dimensions = new ArrayList<String>();
Matcher matcher = Pattern.compile("(?<=\\[).*?(?=\\])").matcher(segments[0]);
while (matcher.find()) {
dimensions.add(matcher.group());
}
/* Look up the SieTemplate (the data structure that contains
the data-type information) that goes with the variable_name_root. */
SieTemplate template = null;
if (rootTemplates != null) {
for (SieTemplate simObject : rootTemplates) {
if (simObject.parameter.equals(variable_name_root)) {
template = simObject;
break;
}
}
}
/* In case this variable is, or contains an array, call
getVariables(template, segments, ...) to expand it into its elements. */
return getVariables(template, segments, 0, dimensions, variable_name_root);
}
/**
* Expand index ranges in an arrayed variable to a list of individual array elements.
*/
List<String> getVariables(SieTemplate template,
String[] segments, int segment_index, ArrayList<String> dimensions, String name) {
ArrayList<String> variables = new ArrayList<String>();
if (dimensions == null) {
/* We haven't yet parsed the current segment for dimensions, so we need to. */
/* Get the name in the current segment, minus anything enclosed in square brackets. */
String trimmedName = segments[segment_index].replaceFirst("\\[.*\\]", "");
dimensions = new ArrayList<String>();
/* Create a Matcher for the dimension-extents enclosed in square brackets, in the current segment. */
Matcher matcher = Pattern.compile("(?<=\\[).*?(?=\\])").matcher(segments[segment_index]);
/* Find, and add each of the dimension-extents to the dimensions ArrayList. */
while (matcher.find()) {
dimensions.add(matcher.group());
}
/* Get the SieTemplate (i.e., the data-type information) for the current segment. */
if (template != null) {
for (SieTemplate child : template.children) {
if (child.parameter.equals(trimmedName)) {
template = child;
break;
}
}
}
/* Recursive call . */
variables.addAll(getVariables(template, segments, segment_index, dimensions, name + "." + trimmedName));
}
else if (!dimensions.isEmpty()) {
/* We've already checked for dimensions (above). */
/* Get/remove the first dimension-index string (there may be more than one).
A dimension-index string form is #, or #-# (a range). */
String value = dimensions.remove(0);
/* Check whether the dimensions represent a range. */
int location = value.indexOf('-');
if (location > -1) {
/* The dimension string contains '-', and therefore represents a range of indices. */
/* Get the first (probably lower) index of the range. */
int first = Integer.parseInt(value.substring(0, location));
/* Get the second (probably upper) index of the range. */
int last = Integer.parseInt(value.substring(location + 1));
/* Create multiple, singly indexed variables for each of the array indices in the range. */
boolean reverse = first > last;
for (int i = first; reverse ? (i >= last) : (i <= last); i += reverse ? -1 : 1) {
variables.addAll(
/* In case this array variable element is itself an array, recursively call
getVariables() to expand it into its elements. */
getVariables(template, segments, segment_index, (ArrayList<String>)dimensions.clone(), name + "[" + i + "]")
);
}
}
else {
/* The dimension string doesn't contain '-', and therefore represents a single array index. */
variables.addAll(
/* In case this array variable element is itself an array, recursively call
getVariables() to expand it into its elements.*/
getVariables(template, segments, segment_index, dimensions, name + "[" + Integer.parseInt(value) + "]")
);
}
}
else if (segment_index == segments.length - 1) {
/* The dimensions are empty, and segment-index points to the last segment,
so we are done expanding this variable, so just add it to the variables array. */
variables.add(name);
}
else {
/* We've checked the current segment for dimensions, and they are empty.
This isn't the last segment, so move on to the next segment. */
variables.addAll(getVariables(template, segments, segment_index + 1, null, name));
}
return variables;
}
/**
* routine to find the segments in the variable name and the subscripts
* gets a message describing the syntax error
*
* @param name String the variable's name to be data recorded.
* @param text the text containing the error
* @param matcher the matcher containing the error
*
* @return a description of the error
*/
private void addVariable(String name) {
String[] segments = name.split("\\.");
for (String segment : segments) {
VariableName tempName = new VariableName();
Matcher matcher = Pattern.compile("\\[.\\]").matcher(segment);
tempName.name = segment.replaceFirst("\\[.*\\]", "");
while (matcher.find()) {
String str_idx = matcher.group().substring(1, matcher.group().length() - 1);
tempName.dimensions.add(Integer.parseInt(str_idx));
}
nameSegment.add(tempName);
}
fullName.add(nameSegment.get(0).name);
addSubscript(0);
for (int i = 1; i < nameSegment.size(); i++) {
addName(nameSegment.get(i).name);
addSubscript(i);
}
for (String aFullName : fullName) {
variables.add(aFullName);
selectedVarList.addData(aFullName);
}
fullName.clear();
nameSegment.clear();
}
String getMessage(String text, Matcher matcher) {
int start = matcher.end() - 6;
int end = matcher.end() + 5;
start = start < 0 ? 0 : start;
end = end > text.length() ? text.length() : end;
return text.substring(start, end);
}
/**
* checks the validity of the variable name
*
* @param name the name in question
* @throws InvalidVariableNameException InvalidVariableNameException
*/
protected void validateVariable(String name) throws InvalidVariableNameException {
name = name.replaceAll("\\s", "");
if (name.isEmpty()) {
throw new InvalidVariableNameException("Variable names may not be empty.");
}
Matcher matcher = Pattern.compile("^(?![a-zA-Z_])").matcher(name);
if (matcher.find()) {
throw new InvalidVariableNameException(
"Variable names must start with a letter\n(a-z, A-Z) or an underscore (_).\n\n" +
getMessage(name, matcher));
}
String reducedText = name.replaceAll("\\[.*?\\]|\\.(?!$)", "");
matcher = Pattern.compile("\\W").matcher(reducedText);
if (matcher.find()) {
throw new InvalidVariableNameException(
"Variable names may only include alphanumeric\n" +
"characters (a-z, A-Z, 0-9) and underscores (_).\n\n" +
getMessage(reducedText, matcher));
}
matcher = Pattern.compile("(?<![\\w\\]])\\[|\\](?!(?:\\[|\\.|$))").matcher(name);
if (matcher.find()) {
throw new InvalidVariableNameException(
"Indices may only appear at the end of members.\n\n" +
getMessage(name, matcher));
}
matcher = Pattern.compile("\\[(?!\\d+(?:-\\d+)?\\])").matcher(name);
if (matcher.find()) {
throw new InvalidVariableNameException(
"Indices may only be of the form [integer] or [integer-integer].\n\n" +
getMessage(name, matcher));
}
}
//========================================
// Inner classes
//========================================
/**
* private class to contain the name and the
* allows the user to specify the indices of each variable to add
*/
class AddVariableDialog extends JDialog {
/** the northern panel */
JXPanel northPanel = new JXPanel(new GridLayout(0, 1));
/** regular expression for finding indices */
Pattern pattern = Pattern.compile("(?<=\\[)\\d+(?=\\])");
/** the ok action */
final AbstractAction okAction = new AbstractAction("OK") {
{
putValue(SHORT_DESCRIPTION, "Add variables.");
putValue(MNEMONIC_KEY, KeyEvent.VK_O);
}
public void actionPerformed(ActionEvent actionEvent) {
try {
ArrayList<String> variables = new ArrayList<String>();
for (Component component : northPanel.getComponents()) {
variables.addAll(((VariablePanel)component).constructVariables());
}
addVariables(variables);
setVisible(false);
}
catch (InvalidVariableNameException invalidVariableNameException) {
JOptionPane.showMessageDialog(getMainFrame(),
invalidVariableNameException.toString(), "Invalid Variable Name", JOptionPane.ERROR_MESSAGE);
}
}
};
/** the ok button */
JXButton okButton = new JXButton(okAction);
/**
* constructor
*/
public AddVariableDialog() {
super(getMainFrame(), "Specify Indices", true);
final AbstractAction cancelAction = new AbstractAction("Cancel") {
{
putValue(SHORT_DESCRIPTION, "Cancel variable addition.");
putValue(MNEMONIC_KEY, KeyEvent.VK_C);
}
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
};
setContentPane(new JXPanel(new BorderLayout()) {{
add(new JScrollPane() {{
setViewportView(new JPanel(new BorderLayout()) {{
add(northPanel, BorderLayout.NORTH);
}});
}}, BorderLayout.CENTER);
add(new JXPanel(new BorderLayout()) {{
add(new JXPanel(new GridLayout(1, 4)) {{
add(Box.createHorizontalGlue());
add(okButton);
add(new JXButton(cancelAction));
add(Box.createHorizontalGlue());
}}, BorderLayout.SOUTH);
}}, BorderLayout.SOUTH);
}});
JRootPane rootPane = getRootPane();
rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "AddVariableDialog.cancel");
rootPane.getActionMap().put("AddVariableDialog.cancel", cancelAction);
rootPane.setDefaultButton(okButton);
}
/**
* launches the dialog with the <code>pendingVariables</code>
*
* @param pendingVariables the variables awaiting addition
*/
public void launch(List<String> pendingVariables) {
for (String variable_name : pendingVariables) {
// If any variables are indexable, launch the dialog.
if (pattern.matcher(variable_name).find()) {
northPanel.removeAll();
for (String var : pendingVariables) {
northPanel.add(new VariablePanel(var));
}
pack();
setLocationRelativeTo(getMainFrame());
getRootPane().getDefaultButton().requestFocusInWindow();
setVisible(true);
return;
}
}
// No indexable variables, just add them all.
addVariables(pendingVariables);
}
/**
* presents a single variable
*/
class VariablePanel extends JXPanel {
/** the variable */
String variable_name;
/** a matcher for finding indices */
Matcher matcher;
/** the segments of the variable's name */
String[] segments;
/** the index specifications */
ArrayList<JComponent> indices = new ArrayList<JComponent>();
/**
* constructor
*
* @param variable the variable to add
*/
public VariablePanel(String variable_name) {
super(new GridBagLayout());
this.variable_name = variable_name;
matcher = pattern.matcher(variable_name);
add(new JXButton(new AbstractAction() {
{
putValue(NAME, "Remove");
putValue(SHORT_DESCRIPTION, "Cancel addition of this variable.");
}
public void actionPerformed(ActionEvent event) {
northPanel.remove(VariablePanel.this);
if (northPanel.getComponentCount() == 0) {
AddVariableDialog.this.setVisible(false);
}
else {
northPanel.revalidate();
}
}
}));
add(Box.createHorizontalStrut(5));
segments = pattern.split(variable_name);
while (matcher.find()) {
int arraySize = Integer.parseInt(matcher.group());
indices.add(arraySize == 0 ? new IndexTextField() : new DoubleComboBox(arraySize));
}
add(new JXLabel() {{
setText(segments[0]);
}});
for (int i = 0; i < indices.size(); ) {
add(indices.get(i));
final String string = segments[++i];
add(new JXLabel() {{
setText(string);
}});
}
add(Box.createHorizontalGlue(), new GridBagConstraints() {{
weightx = 1;
}});
}
/**
* Constructs the fully qualified name of the variable
*
* @return the variable's name
*/
String constructName() {
String name = "";
for (int i = 0; i < segments.length; ++i) {
name += segments[i];
if (i < indices.size()) {
name += indices.get(i);
}
}
return name;
}
/**
* Returns the variables, with indices expanded, represented by this panel
*
* @param all variables represented by this panel
*/
public Collection<String> constructVariables()
throws InvalidVariableNameException {
return getVariables(constructName()); /* getVariables#1 */
}
}
}
/**
* Used in the constructor for Variable Panel.
* Allows the user to specifiy an index range for pointers
*/
class IndexTextField extends JXTextField {
public IndexTextField() {
setText("0-0");
setColumns(4);
setHorizontalAlignment(CENTER);
}
@Override
public String toString() {
return getText();
}
}
/**
* Private class to contain the name and the
* dimensions for each segment of variable added.
*/
private class VariableName {
@ -888,7 +1225,6 @@ public class DreApplication extends TrickApplication {
*/
private class TreeMouseListener extends MouseAdapter {
//========================================
// MouseListener methods
//========================================
@ -943,7 +1279,9 @@ public class DreApplication extends TrickApplication {
}
}
} else if (clickedNode != null && varTree.getModel().isLeaf(clickedNode) && clickedNode.isTrickManaged()) {
addVariable(SieTreeModel.getPathName(clickedPath) + clickedNode);
ArrayList<String> pendingVariables = new ArrayList<String>();
pendingVariables.add( SieTreeModel.getPathName(clickedPath) + clickedNode);
addVariableDialog.launch(pendingVariables);
}
}
@ -955,4 +1293,10 @@ public class DreApplication extends TrickApplication {
}
}
/** exception thrown for poorly formed variable names */
class InvalidVariableNameException extends Exception {
public InvalidVariableNameException(String description) {
super(description);
}
}
}