/* ==============================================
* Simtools : The tools library used in JSynoptic
* ==============================================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2003, by :
* Corporate:
* Astrium SAS
* EADS CRC
* Individual:
* Nicolas Brodu
*
* $Id: GenericMapper.java,v 1.17 2008/04/10 16:39:29 ogor Exp $
*
* Changes
* -------
* 25-Sep-2003 : Initial public release (NB);
* 07-Oct-03: Implemented all edit dialogs (NB);
* 03-Nov-03 : Separated generic and color part (NB)
*
*/
package simtools.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import simtools.data.DataException;
import simtools.data.DataSource;
import simtools.util.ListenerManager;
import simtools.util.NumberStringComparator;
/**
* This class maps data sources and index to generic java Objects.
* The mapping is user defined, and based on the value of the data source.
*
* The data source shall not be saved into the object. Rather, this class goal is to implement
* a mapping independent of the data source given.
*
*/
public class GenericMapper implements Serializable, Cloneable {
static final long serialVersionUID = -3336873194029698696L;
public static ResourceBundle resources = ResourceFinder.get(GenericMapper.class);
/** Associate a mappedValue for a data value of any type
*/
protected TreeMap map;
protected Vector intervals;
protected String name;
protected Object defaultValue; // null if not specified
protected transient ListenerManager listeners = new ListenerManager();
public Object clone() throws CloneNotSupportedException {
GenericMapper m = (GenericMapper)super.clone();
// Don't clone key and values, as they are not modified but only deleted/inserted
m.map = (TreeMap)map.clone();
// Intervals are cloned on write.
m.intervals = (Vector)intervals.clone();
m.listeners = new ListenerManager();
m.name = (name != null)? resources.getString("CopyOf") + name : null;
return m;
}
protected void setMapperValues(GenericMapper m){
map = m.map;
intervals = m.intervals;
}
/**
* @return all values the mapper can provide
*/
protected ArrayList getAllMapperValues(){
ArrayList ret = new ArrayList();
// Update map values
Set mapKeys = map.keySet();
for(Iterator it=mapKeys.iterator();it.hasNext();){
ret.add(map.get(it.next()));
}
// Update interval values
for(int i=0;i<intervals.size();i++){
ret.add(((Interval)intervals.get(i)).value);
}
return ret;
}
public void addListener(MapperListener l) {
listeners.add(l);
}
public void removeListener(MapperListener l) {
listeners.remove(l);
}
protected void notifyListeners() {
synchronized(listeners) {
int n = listeners.size(); // only one call outside loop
for (int i=0; i<n; ++i) {
MapperListener ml = (MapperListener)listeners.get(i);
if (ml!=null) ml.mappingChanged(this);
}
}
}
/** Associate a value for a data interval.
* The data interval can be of type double, long, or String,
* and the comparison will use the best possible conversion.
* Ex: you can specify an interval 0xABCDEF0123456789 < x < 0xABCDEF0123456791
* and 0xABCDEF0123456790 will match (which would not be possible with the
* precision of "double" data)
*/
public static class Interval implements Cloneable, Serializable {
/**
*
*/
private static final long serialVersionUID = 1724762219212741694L;
protected int type; // 0, 1, 2, 3 = double, long, string, degenerated
protected double dmin, dmax;
protected long lmin, lmax;
protected String smin, smax;
protected boolean boundedMin, boundedMax;
protected boolean infiniteMin, infiniteMax;
/** The value associated with this interval */
public Object value;
/**
* Creates an interval of "double" data with a simple bound
* @param isMore Values in the interval are greater than the v argument
* @param v The value to consider as the interval limit
* @param bounded If the value itself is in the interval
* @param value The value to associate with this interval
*/
public Interval(boolean isMore, double v, boolean bounded, Object value) {
type = 0;
this.value = value;
if (isMore) {
dmin = v; dmax = Double.POSITIVE_INFINITY;
lmin = (long)dmin; lmax = Long.MAX_VALUE;
this.boundedMin = bounded; this.boundedMax = true;
infiniteMax = true;
} else {
dmin = Double.NEGATIVE_INFINITY; dmax = v;
lmin = Long.MIN_VALUE; lmax = (long)dmax;
this.boundedMin = true; this.boundedMax = bounded;
infiniteMin = true;
}
}
public Interval(double min, boolean boundedMin, double max, boolean boundedMax, Object value) {
type = 0;
dmin = min; dmax = max;
lmin = (long)min; lmax = (long)max;
this.boundedMin = boundedMin; this.boundedMax = boundedMax;
this.value = value;
}
/**
* Creates an interval of "long" data with a simple bound
* @param isMore Values in the interval are greater than the v argument
* @param v The value to consider as the interval limit
* @param bounded If the value itself is in the interval
* @param value The value to associate with this interval
*/
public Interval(boolean isMore, long v, boolean bounded, Object value) {
type = 1;
this.value = value;
if (isMore) {
dmin = v; dmax = Double.POSITIVE_INFINITY;
lmin = v; lmax = Long.MAX_VALUE;
this.boundedMin = bounded; this.boundedMax = true;
infiniteMax = true;
} else {
dmin = Double.NEGATIVE_INFINITY; dmax = v;
lmin = Long.MIN_VALUE; lmax = v;
this.boundedMin = true; this.boundedMax = bounded;
infiniteMin = true;
}
}
public Interval(long min, boolean boundedMin, long max, boolean boundedMax, Object value) {
type = 1;
dmin = (double)min; dmax = (double)max;
lmin = min; lmax = max;
this.boundedMin = boundedMin; this.boundedMax = boundedMax;
this.value = value;
}
/**
* Creates an interval of "String" data with a simple bound
* @param isMore Values in the interval are greater than the v argument
* @param v The value to consider as the interval limit
* @param bounded If the value itself is in the interval
* @param value The value to associate with this interval
*/
public Interval(boolean isMore, String v, boolean bounded, Object value) {
type = 2;
this.value = value;
if (isMore) {
smin = v; smax = null;
this.boundedMin = bounded; this.boundedMax = false;
infiniteMax = true;
} else {
smin = null; smax = v;
this.boundedMin = false; this.boundedMax = bounded;
infiniteMin = true;
}
}
public Interval(String min, boolean boundedMin, String max, boolean boundedMax, Object value) {
type = 2;
smin = min; smax = max;
this.boundedMin = boundedMin; this.boundedMax = boundedMax;
this.value = value;
}
/**
* Creates a degenarated interval always including any value
*/
public Interval(Object value) {
type = 3;
this.value = value;
infiniteMax = true;
infiniteMin = true;
boundedMin = true;
boundedMax = true;
}
public boolean contains(double value) {
if (type==3) return true;
if (type==2) return false;
if (value == dmin) return boundedMin;
if (value == dmax) return boundedMax;
if (infiniteMin && infiniteMax) return true;
if (infiniteMin) return value < dmax;
if (infiniteMax) return value > dmin;
if ((value > dmin) && (value<dmax)) return true;
return false;
}
public boolean contains(long value) {
if (type==3) return true;
if (type==2) return false;
if (value == lmin) return boundedMin;
if (value == lmax) return boundedMax;
if (infiniteMin && infiniteMax) return true;
if (infiniteMin) return value < lmax;
if (infiniteMax) return value > lmin;
if ((value > lmin) && (value<lmax)) return true;
return false;
}
public boolean contains(String value) {
if (type==3) return true;
if (type!=2) return false;
if (value.compareTo(smin)==0) return boundedMin;
if (value.compareTo(smax)==0) return boundedMax;
if (infiniteMin && infiniteMax) return true;
if (infiniteMin) return value.compareTo(smax) < 0;
if (infiniteMax) return value.compareTo(smin) > 0;
if ((value.compareTo(smin) > 0) && (value.compareTo(smax) < 0)) return true;
return false;
}
public String toString() {
if (type==3) return resources.getString("AnyValue");
String ret = "";
if (!infiniteMin) {
switch(type) {
case 0: ret += dmin; break;
case 1: ret += lmin; break;
case 2: ret += smin; break;
}
ret += " <";
if (boundedMin) ret+= "=";
ret += " ";
}
ret += resources.getString("Value");
if (!infiniteMax) {
ret += " <";
if (boundedMax) ret+= "=";
ret += " ";
switch(type) {
case 0: ret += dmax; break;
case 1: ret += lmax; break;
case 2: ret += smax; break;
}
}
return ret;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Number getMin() throws NumberFormatException{
Number ret = null;
switch(type) {
case 0: ret = new Double(dmin); break;
case 1: ret = new Long(lmin); break;
case 2: ret = new Double(smin); break;
}
return ret;
}
public Number getMax() throws NumberFormatException{
Number ret = null;
switch(type) {
case 0: ret = new Double(dmax); break;
case 1: ret = new Long(lmax); break;
case 2: ret = new Double(smax); break;
}
return ret;
}
public boolean isBoundedMin() {
return boundedMin;
}
public boolean isBoundedMax() {
return boundedMax;
}
public Object getValue() {
return value;
}
}
public GenericMapper() {
this(null);
}
public GenericMapper(String name) {
this.name = name;
map = new TreeMap(new NumberStringComparator());
intervals = new Vector();
}
/**
* Realizes the mapping, and returns the object for this data source and index
* @param ds The data source to map
* @param index The index in the data source (may be used, or not)
* @return the mapped object, or null
*/
public Object getMapping(DataSource ds, long index) {
Object value;
try {
if (ds==null)
return null;
value = ds.getValue(index);
} catch (DataException e) {
return defaultValue; // No data => no value
}
return getMapping(value);
}
/**
* Realizes the mapping, and returns the object for this data source and index
* @param ds The data source to map
* @param index The index in the data source (may be used, or not, and defaults to the last index is used)
* @return the mapped object, or null
*/
public Object getMapping(DataSource ds) {
Object value;
try {
if (ds==null)
return null;
value = ds.getValue(ds.getLastIndex());
} catch (DataException e) {
return defaultValue; // No data => no value
}
return getMapping(value);
}
public Object getMapping(Object value) {
// Look for exact value match
Object p = map.get(value);
if (p!=null) return p;
int type = -1;
if ((value instanceof Double) || (value instanceof Float)) {
type = 0;
}
if ((value instanceof Long) || (value instanceof Integer) || (value instanceof Short) || (value instanceof Byte)) {
type = 1;
}
if (value instanceof String) type = 2;
// Unknown data type, cannot match an interval
if (type==-1) return defaultValue;
// Look in the intervals
for (int i=0; i<intervals.size(); ++i) {
Interval iv = (Interval)intervals.get(i);
switch(type) {
case 0:
if (iv.contains(((Number)value).doubleValue())) return iv.value;
else continue;
case 1:
if (iv.contains(((Number)value).longValue())) return iv.value;
else continue;
case 2:
if (iv.contains((String)value)) return iv.value;
}
}
return defaultValue;
}
/** The value to return when nothing match */
public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
}
/** The value returned when nothing match */
public Object getDefaultValue() {
return defaultValue;
}
/** Associate a mappedValue with a value */
public void setMapping(Object value, Object mappedValue) {
map.put(value,mappedValue);
notifyListeners();
}
/** Associate a mappedValue with a double value interval (bounds included) */
public void setMapping(double minvalue, double maxvalue, Object mappedValue) {
setMapping(minvalue, true, maxvalue, true, mappedValue);
}
/** Associate a mappedValue with a double value interval. Interval bounds are specified */
public void setMapping(double minvalue, boolean boundedMin, double maxvalue, boolean boundedMax, Object mappedValue) {
intervals.add(new Interval(minvalue,boundedMin,maxvalue,boundedMax,mappedValue));
notifyListeners();
}
/** Associate a mappedValue with a double value infinite range (bound included).
* @param isMore if true, the interval is x >= value, else it is x <= value
*/
public void setMapping(boolean isMore, double value, Object mappedValue) {
setMapping(isMore, value, true, mappedValue);
}
/** Associate a mappedValue with a double value infinite range.
* @param isMore if true, the interval is x >= value, else it is x <= value
*/
public void setMapping(boolean isMore, double value, boolean boundIncluded, Object mappedValue) {
intervals.add(new Interval(isMore, value, boundIncluded, mappedValue));
notifyListeners();
}
/** Associate a mappedValue with a long value interval (bounds included) */
public void setMapping(long minvalue, long maxvalue, Object mappedValue) {
setMapping(minvalue, true, maxvalue, true, mappedValue);
}
/** Associate a mappedValue with a long value interval. Interval bounds are specified */
public void setMapping(long minvalue, boolean boundedMin, long maxvalue, boolean boundedMax, Object mappedValue) {
intervals.add(new Interval(minvalue,boundedMin,maxvalue,boundedMax,mappedValue));
notifyListeners();
}
/** Associate a mappedValue with a long value infinite range (bound included).
* @param isMore if true, the interval is x >= value, else it is x <= value
*/
public void setMapping(boolean isMore, long value, Object mappedValue) {
setMapping(isMore, value, true, mappedValue);
}
/** Associate a mappedValue with a long value infinite range.
* @param isMore if true, the interval is x >= value, else it is x <= value
*/
public void setMapping(boolean isMore, long value, boolean boundIncluded, Object mappedValue) {
intervals.add(new Interval(isMore, value, boundIncluded, mappedValue));
notifyListeners();
}
/** Associate a mappedValue with a String value interval (bounds included) */
public void setMapping(String minvalue, String maxvalue, Object mappedValue) {
setMapping(minvalue, true, maxvalue, true, mappedValue);
}
/** Associate a mappedValue with a String value interval. Interval bounds are specified */
public void setMapping(String minvalue, boolean boundedMin, String maxvalue, boolean boundedMax, Object mappedValue) {
intervals.add(new Interval(minvalue,boundedMin,maxvalue,boundedMax,mappedValue));
notifyListeners();
}
/** Associate a mappedValue with a String value infinite range (bound included).
* @param isMore if true, the interval is x >= value, else it is x <= value
*/
public void setMapping(boolean isMore, String value, Object mappedValue) {
setMapping(isMore, value, true, mappedValue);
}
/** Associate a mappedValue with a String value infinite range.
* @param isMore if true, the interval is x >= value, else it is x <= value
*/
public void setMapping(boolean isMore, String value, boolean boundIncluded, Object mappedValue) {
intervals.add(new Interval(isMore, value, boundIncluded, mappedValue));
notifyListeners();
}
public String toString() {
if (name!=null){
return name;
}
return super.toString();
}
/**
* Set mapper name
* @param name
*/
public void setName(String name){
this.name = name;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (!(obj instanceof GenericMapper)) return false;
GenericMapper c = (GenericMapper)obj;
if ((name == null) && (c.name==null)) return super.equals(obj);
if ((name == null) || (c.name==null)) return false;
return name.equals(c.name);
}
public static GenericMapper createMapperDialog(JDialog owner) {
GenericMapper cm = new GenericMapper();
cm.editDialog(owner);
return cm;
}
public MapperTableModel createModel() {
return new MapperTableModel();
}
/** For subclasses to specialize */
protected ExpressionMappingTable createTable(JDialog parent) {
return new ExpressionMappingTable(parent);
}
/** For subclasses to specialize, create a new object of the specialized type */
protected Object createNewValue() {
return new Double(Math.random());
}
/**
* Creates a panel to configure this Mapper.
* @param owner An optional owner frame
* @param listener An optional listener for the title and the OK button. If null, these components won't be displayed
* <p>The action listener can be sent two action id:<ul>
* <li>1 => the text field content changed. The new text is available in the action "command" string</li>
* <li>2 => the ok button was pressed</li>
* </ul>the action source is the returned panel</p>
*/
public JPanel createPanel(JDialog owner, ActionListener listener) {
final ExpressionMappingTable table = createTable(owner);
final JPanel pane = new JPanel(new BorderLayout());
final ActionListener actionListener = listener; // make it visible for anonymous classes
Box namePanel = Box.createHorizontalBox();
final JTextField tfName = new JTextField(20);
namePanel.add(new JLabel(resources.getString("MapperName:")));
namePanel.add(Box.createHorizontalGlue());
namePanel.add(tfName);
tfName.setText((name==null) ? "" : name);
if (listener!=null) pane.add(namePanel, BorderLayout.NORTH);
Box leftPane = Box.createVerticalBox();
leftPane.add(new JLabel(resources.getString("CurrentAssociations(double-clickToEdit)")));
//Create the scroll pane and add the table to it.
leftPane.add(new JScrollPane(table));
pane.add(leftPane, BorderLayout.CENTER);
final JButton newv, newi, del, up, down, ok;
JPanel rightPane = new JPanel(new BorderLayout());
JPanel buttons = new JPanel(new GridLayout(5,1));
buttons.add(newv =new JButton(resources.getString("NewValue")));
buttons.add(newi = new JButton(resources.getString("NewInterval")));
buttons.add(del = new JButton(resources.getString("Delete")));
buttons.add(up = new JButton(resources.getString("MoveUp")));
buttons.add(down = new JButton(resources.getString("MoveDown")));
rightPane.add(buttons, BorderLayout.NORTH);
ok = new JButton(resources.getString("OK"));
if (listener!=null) rightPane.add(ok, BorderLayout.SOUTH);
pane.add(rightPane, BorderLayout.EAST);
del.setEnabled(false);
up.setEnabled(false);
down.setEnabled(false);
if (listener!=null) tfName.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
updateName();
}
public void removeUpdate(DocumentEvent e) {
updateName();
}
public void changedUpdate(DocumentEvent e) {
updateName();
}
public void updateName() {
name = tfName.getText();
if (name.equals("")) name = null;
actionListener.actionPerformed(new ActionEvent(pane, 1, (name==null) ? resources.getString("UnnamedMapper") : name));
}
});
table.addSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
int r = table.getSelectedRow();
if (r==-1) {
del.setEnabled(false);
up.setEnabled(false);
down.setEnabled(false);
return;
}
del.setEnabled(true);
Expression e = (Expression)table.tableModel.expressions.get(r);
if (e.value instanceof Interval) {
up.setEnabled((r>0) && (((Expression)table.tableModel.expressions.get(r-1)).value instanceof Interval));
down.setEnabled(r<table.tableModel.expressions.size()-1);
} else {
up.setEnabled(false);
down.setEnabled(false);
}
}
});
newv.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Long key = null;
for (int k = 0; k < Integer.MAX_VALUE; k++) {
key = new Long(k);
if (map.get(key)==null) break;
}
map.put(key, createNewValue());
int r = 0;
for (Iterator it = map.keySet().iterator(); it.hasNext(); r++) {
if (it.next().equals(key)) break;
}
table.tableModel.update(0,map.size()+intervals.size()-1);
table.tableModel.fireTableRowsInserted(r,r);
table.setRowSelectionInterval(r,r);
}
});
newi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
intervals.add(new Interval(false,0,false,createNewValue()));
int r = map.size()+intervals.size()-1;
table.tableModel.fireTableRowsInserted(r,r);
table.tableModel.update(0,r);
table.setRowSelectionInterval(r,r);
}
});
del.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int r = table.getSelectedRow();
if (r<0) return;
Expression exp = (Expression)table.tableModel.expressions.get(r);
if (r<map.size()) {
map.remove(exp.value);
table.tableModel.fireTableRowsDeleted(r,r);
table.tableModel.update(0,map.size()+intervals.size()-1);
return;
}
intervals.remove(exp.value);
table.tableModel.fireTableRowsDeleted(r,r);
table.tableModel.update(0,map.size()+intervals.size()-1);
}
});
up.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int r = table.getSelectedRow();
int i = r - map.size();
if ((i<1) || (i>=intervals.size())) return;
Object o = intervals.get(i-1);
Object p = intervals.get(i);
intervals.set(i-1,p);
intervals.set(i,o);
table.tableModel.update(r-1,r);
table.setRowSelectionInterval(r-1,r-1);
}
});
down.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int r = table.getSelectedRow();
int i = r - map.size();
if ((i<0) || (i>=intervals.size()-1)) return;
Object o = intervals.get(i);
Object p = intervals.get(i+1);
intervals.set(i,p);
intervals.set(i+1,o);
table.tableModel.update(r,r+1);
table.setRowSelectionInterval(r+1,r+1);
}
});
if (listener!=null) ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
actionListener.actionPerformed(new ActionEvent(pane, 2,""));
}
});
return pane;
}
public void editDialog(JDialog owner) {
final JDialog dialog = new JDialog(owner,true);
dialog.setTitle((name==null) ? resources.getString("UnnamedMapper") : name);
JPanel content = createPanel(owner, new ActionListener() {
public void actionPerformed(ActionEvent e) {
switch (e.getID()) {
case 1: // text change
dialog.setTitle(e.getActionCommand());
break;
case 2: // OK pressed
dialog.dispose();
break;
}
}
});
dialog.getContentPane().add(content);
dialog.pack();
dialog.show();
notifyListeners(); // there is no cancel => always consider the mapping changed
}
/** Copied from Java Tutorial on tables, they had the very good idea to set up a colored cell table :))))
*/
protected class ExpressionMappingTable extends JTable {
/**
*
*/
private static final long serialVersionUID = -4343304998955053415L;
protected MapperTableModel tableModel;
public ExpressionMappingTable(JDialog parent) {
setModel(tableModel = createModel());
setPreferredScrollableViewportSize(new Dimension(300, 200));
getColumnModel().getColumn(0).setPreferredWidth(250);
getColumnModel().getColumn(1).setPreferredWidth(50);
//Set up renderer and editor for the value column.
setUpRenderer();
setUpEditor();
setUpExpressionEditor(parent);
getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
public void addSelectionListener(ListSelectionListener l) {
getSelectionModel().addListSelectionListener(l);
}
protected void setUpRenderer() {
}
protected void setUpEditor() {
}
protected void setUpExpressionEditor(JDialog parent) {
//First, set up the button that brings up the dialog.
final JButton button = new JButton("");
button.setBackground(Color.white);
button.setBorderPainted(false);
button.setMargin(new Insets(0,0,0,0));
//Now create an editor to encapsulate the button, and
//set it up as the editor for all Color cells.
final ExpressionEditor expressionEditor = new ExpressionEditor(button);
setDefaultEditor(Expression.class, expressionEditor);
final ExpressionDialog expressionDialog = new ExpressionDialog(parent);
//Set up the dialog that the button brings up.
expressionDialog.addOKListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
expressionEditor.currentExpression = expressionDialog.getExpression();
}
});
//Here's the code that brings up the dialog.
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
button.setText(expressionEditor.currentExpression.toString());
expressionDialog.setLocationRelativeTo(button);
expressionDialog.edit(expressionEditor.currentExpression);
}
});
}
}
/*
* The editor button that brings up the dialog.
* We extend DefaultCellEditor for convenience,
* even though it mean we have to create a dummy
* check box. Another approach would be to copy
* the implementation of TableCellEditor methods
* from the source code for DefaultCellEditor.
*/
protected static class ExpressionEditor extends DefaultCellEditor {
/**
*
*/
private static final long serialVersionUID = 9202536695519603668L;
protected Expression currentExpression;
public ExpressionEditor(JButton b) {
super(new JCheckBox()); //Unfortunately, the constructor
//expects a check box, combo box,
//or text field.
editorComponent = b;
setClickCountToStart(2); //This is usually 1 or 2.
//Must do this so that editing stops when appropriate.
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
fireEditingStopped();
}
});
}
protected void fireEditingStopped() {
super.fireEditingStopped();
}
public Object getCellEditorValue() {
return currentExpression;
}
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int row,
int column) {
((JButton)editorComponent).setText(value.toString());
currentExpression = (Expression)value;
return editorComponent;
}
}
protected static class Expression implements Cloneable {
public Object value;
public Expression(Object o) {
value = o;
}
public String toString() {
if (value instanceof Interval) return ((Interval)value).toString();
return resources.getString("Value=") + value;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
protected class MapperTableModel extends AbstractTableModel {
/**
*
*/
private static final long serialVersionUID = -8126260990866927148L;
final String[] columnNames = {resources.getString("Expression"), resources.getString("Mapping")};
protected Object[] mappedValues;
protected Vector expressions;
public MapperTableModel() {
// converts the map into two arrays
expressions = new Vector();
if ((map!=null) && (map.keySet()!=null)) for (Iterator it = map.keySet().iterator(); it.hasNext(); ) {
expressions.add(new Expression(it.next()));
}
if (intervals!=null) for (Iterator it = intervals.iterator(); it.hasNext(); ) {
expressions.add(new Expression(it.next()));
}
if (map.values()!=null) mappedValues = map.values().toArray();
}
/**
* Updates the model, called when the intervals Vector changed
* @param startIndex
* @param endIndex
*/
public void update(int startIndex, int endIndex) {
// converts the map into two arrays
expressions = new Vector();
for (Iterator it = map.keySet().iterator(); it.hasNext(); ) {
expressions.add(new Expression(it.next()));
}
for (Iterator it = intervals.iterator(); it.hasNext(); ) {
expressions.add(new Expression(it.next()));
}
mappedValues = map.values().toArray();
fireTableRowsUpdated(startIndex, endIndex);
}
public int getColumnCount() {
return columnNames.length;
}
public Class getColumnClass(int c) {
if (c==0) return Expression.class;
return Object.class;
}
public int getRowCount() {
return expressions.size();
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
if (col==0) return expressions.get(row);
if (row<mappedValues.length) return mappedValues[row];
return ((Interval)intervals.get(row-mappedValues.length)).value;
}
/*
* Don't need to implement this method unless your table's
* editable.
*/
public boolean isCellEditable(int row, int col) {
if (col==0) return true;
return false;
}
public void setValueAt(Object value, int row, int col) {
// first rows are backed by the map
if (row<mappedValues.length) {
// change in value => ouch, change the map key
if (col==0) {
Object oldvalue = ((Expression)expressions.get(row)).value;
Object newvalue = mappedValues[row];
map.remove(oldvalue);
map.put(((Expression)value).value,newvalue);
expressions = new Vector();
for (Iterator it = map.keySet().iterator(); it.hasNext(); ) {
expressions.add(new Expression(it.next()));
}
for (Iterator it = intervals.iterator(); it.hasNext(); ) {
expressions.add(new Expression(it.next()));
}
mappedValues = map.values().toArray();
this.fireTableRowsUpdated(0,mappedValues.length-1);
return;
}
// change in mappedValue
Object key = ((Expression)expressions.get(row)).value;
if (!map.containsKey(key)) return; // shall not happen
map.put(key,value); // put the new value
mappedValues[row] = value; // update our own copy
this.fireTableCellUpdated(row,col);
return;
}
// last rows are backed by the intervals
if (col==0) {
intervals.set(row-mappedValues.length,((Expression)value).value);
expressions.set(row,value);
this.fireTableRowsUpdated(row,row);
return;
}
// Clone on modification, useful for cloning the mapper and share the values as long as they
// do not change => same for the treemap, the entries are re-inserted
Interval iv = (Interval)intervals.get(row-mappedValues.length);
try {
iv = (Interval)iv.clone();
} catch (CloneNotSupportedException e) {
}
iv.value = value;
intervals.set(row-mappedValues.length,iv);
}
}
public static class ExpressionDialog extends JDialog {
/**
*
*/
private static final long serialVersionUID = 2382730947499900807L;
JButton ok, cancel;
Expression current;
ActionCheckBox mincb, maxcb;
JTextField minValue, maxValue, theValue;
JCheckBox minInc, maxInc;
int mint, maxt;
long lmin, lmax;
double dmin, dmax;
String smin, smax;
int type;
Box valuePanel, intervalPanel;
public ExpressionDialog(JDialog parent) {
super(parent,true);
setTitle(resources.getString("Editing:")+current);
this.getContentPane().setLayout(new BoxLayout(this.getContentPane(),BoxLayout.Y_AXIS));
valuePanel = Box.createHorizontalBox();
valuePanel.add(new JLabel(resources.getString("Value=")));
valuePanel.add(theValue = new JTextField(10));
valuePanel.setVisible(false);
this.getContentPane().add(valuePanel);
Box box;
intervalPanel = Box.createVerticalBox();
box = Box.createHorizontalBox();
box.add(mincb = new ActionCheckBox(resources.getString("Min"), true) {
public void actionPerformed(ActionEvent e) {
minValue.setEnabled(isSelected());
minInc.setEnabled(isSelected());
}
});
box.add(minValue = new JTextField(10));
minValue.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
updateType();
}
public void removeUpdate(DocumentEvent e) {
updateType();
}
public void changedUpdate(DocumentEvent e) {
updateType();
}
});
box.add(minInc = new JCheckBox(resources.getString("BoundIncluded"), true));
intervalPanel.add(box);
box = Box.createHorizontalBox();
box.add(maxcb = new ActionCheckBox(resources.getString("Max"), true) {
public void actionPerformed(ActionEvent e) {
maxValue.setEnabled(isSelected());
maxInc.setEnabled(isSelected());
}
});
box.add(maxValue = new JTextField(10));
maxValue.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
updateType();
}
public void removeUpdate(DocumentEvent e) {
updateType();
}
public void changedUpdate(DocumentEvent e) {
updateType();
}
});
box.add(maxInc = new JCheckBox(resources.getString("BoundIncluded"), true));
intervalPanel.add(box);
intervalPanel.setVisible(false);
this.getContentPane().add(intervalPanel);
mincb.apply();
maxcb.apply();
box = Box.createHorizontalBox();
box.add(Box.createHorizontalGlue());
box.add(cancel = new JButton(resources.getString("Cancel")));
box.add(ok = new JButton(resources.getString("OK")));
this.getContentPane().add(box);
ok.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
updateExpression();
hide();
}
});
cancel.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
hide();
}
});
setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
//pack();
setResizable(false);
}
protected void updateType() {
if (!(current.value instanceof Interval)) return;
smin = minValue.getText();
try {
// If it can be parsed as an integer number, including hex, use that
lmin = Long.decode(smin).longValue();
dmin = lmin;
mint = 1;
} catch (NumberFormatException nfe) {
try {
// If it can be parsed as an integer number, including hex, use that
dmin = Double.parseDouble(smin);
mint = 0;
} catch (NumberFormatException nfe2) {
mint = 2; // and smin already set
}
}
smax = maxValue.getText();
try {
// If it can be parsed as an integer number, including hex, use that
lmax = Long.decode(smax).longValue();
dmax = lmax;
maxt = 1;
} catch (NumberFormatException nfe) {
try {
// If it can be parsed as an integer number, including hex, use that
dmax = Double.parseDouble(smax);
maxt = 0;
} catch (NumberFormatException nfe2) {
maxt = 2; // and smax already set
}
}
if (mincb.isSelected() && maxcb.isSelected()) {
if ((mint==2) || (maxt==2)) type = 2; // one string => string
else if ((mint==1) && (maxt==1)) type = 1; // both integers => integer
else type = 0; // no string and floating point value => floating point
} else if (mincb.isSelected()) {
type = mint;
} else if (maxcb.isSelected()) {
type = maxt;
} else type = 3;
}
protected void updateExpression() {
if (current==null) return;
if (current.value instanceof Interval) {
if (minValue.getText().equals("")) {mincb.setSelected(false); mincb.apply();}
if (maxValue.getText().equals("")) {maxcb.setSelected(false); maxcb.apply();}
Object value = ((Interval)current.value).value;
updateType();
switch(type) {
case 0:
if (mincb.isSelected() && maxcb.isSelected())
current.value = new Interval(dmin, minInc.isSelected(), dmax, maxInc.isSelected(), value);
else if (mincb.isSelected())
current.value = new Interval(true, dmin, minInc.isSelected(), value);
else
current.value = new Interval(false, dmax, maxInc.isSelected(), value);
break;
case 1:
if (mincb.isSelected() && maxcb.isSelected())
current.value = new Interval(lmin, minInc.isSelected(), lmax, maxInc.isSelected(), value);
else if (mincb.isSelected())
current.value = new Interval(true, lmin, minInc.isSelected(), value);
else
current.value = new Interval(false, lmax, maxInc.isSelected(), value);
break;
case 2:
if (mincb.isSelected() && maxcb.isSelected())
current.value = new Interval(smin, minInc.isSelected(), smax, maxInc.isSelected(), value);
else if (mincb.isSelected())
current.value = new Interval(true, smin, minInc.isSelected(), value);
else
current.value = new Interval(false, smax, maxInc.isSelected(), value);
break;
case 3:
current.value = new Interval(value);
break;
}
return; // Interval updated
}
// Not an interval
String value = theValue.getText();
try {
// If it can be parsed as an integer number, including hex, use that
current.value = Long.decode(value);
} catch (NumberFormatException nfe) {
try {
// If it can be parsed as double, OK
current.value = new Double(Double.parseDouble(value));
} catch (NumberFormatException nfe2) {
// Otherwise, keep string
current.value = value;
}
}
}
/**
* @param object
*/
public void edit(Expression e) {
copyExpression(e);
show();
}
/**
* @param object
*/
public void copyExpression(Expression e) {
try {
current = (Expression)e.clone();
} catch (CloneNotSupportedException e1) {
current = e;
}
if (current.value instanceof Interval) {
Interval iv = (Interval)current.value;
mincb.setSelected(!iv.infiniteMin);
mincb.apply();
maxcb.setSelected(!iv.infiniteMax);
maxcb.apply();
switch(iv.type) {
case 0:
if (iv.infiniteMin) minValue.setText("");
else minValue.setText(""+iv.dmin);
if (iv.infiniteMax) maxValue.setText("");
else maxValue.setText(""+iv.dmax);
break;
case 1:
if (iv.infiniteMin) minValue.setText("");
else minValue.setText(""+iv.lmin);
if (iv.infiniteMax) maxValue.setText("");
else maxValue.setText(""+iv.lmax);
break;
case 2:
if (iv.infiniteMin) minValue.setText("");
else minValue.setText(iv.smin);
if (iv.infiniteMax) maxValue.setText("");
else maxValue.setText(iv.smax);
break;
case 3:
minValue.setText("");
maxValue.setText("");
break;
}
valuePanel.setVisible(false);
intervalPanel.setVisible(true);
minInc.setSelected(iv.boundedMin);
maxInc.setSelected(iv.boundedMax);
} else {
theValue.setText(current.value.toString());
intervalPanel.setVisible(false);
valuePanel.setVisible(true);
}
setTitle(resources.getString("Editing:")+current);
setResizable(true);
pack();
setResizable(false);
}
/**
* @param listener
*/
public void addOKListener(ActionListener listener) {
ok.addActionListener(listener);
}
/**
* @return
*/
public Expression getExpression() {
return current;
}
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
listeners = new ListenerManager();
}
public Vector getIntervals() {
return intervals;
}
public TreeMap getMap() {
return map;
}
}