package editor;
import org.drools.semantics.Thing;
import org.drools.semantics.util.editor.FactGraphAnalyzer;
import org.drools.semantics.util.editor.ObjectDescriptor;
import org.drools.semantics.util.editor.RelationDescriptor;
import prefuse.Visualization;
import prefuse.controls.ControlAdapter;
import prefuse.data.*;
import prefuse.visual.AggregateItem;
import prefuse.visual.AggregateTable;
import prefuse.visual.VisualItem;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.lang.reflect.Method;
import java.util.*;
public class EditControlAdapter extends ControlAdapter {
private static class EditGUI {
private JPanel mainPanel = new JPanel( new BorderLayout() );
private JTextField currentNode;
private JComboBox availProps;
private JPanel cards = new JPanel( new CardLayout() );
private JPanel dataCard = new JPanel();
private JPanel objectCard = new JPanel();
private final EditControlAdapter editor;
private Tuple currentTuple;
public JPanel getPanel() {
return mainPanel;
}
public EditGUI( final EditControlAdapter editor ) {
this.editor = editor;
Box info = new Box(BoxLayout.Y_AXIS);
info.setBorder( BorderFactory.createTitledBorder( "Property Editor" ) );
JPanel inner = new JPanel( new GridLayout( 2, 2 ) );
inner.add( new JLabel( "Current Node :" ) );
currentNode = new JTextField();
currentNode.setEditable( false );
inner.add( currentNode );
inner.add( new JLabel( "Available Properties " ) );
availProps = new JComboBox();
availProps.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println(" Changed avail Proprs " + availProps.getSelectedItem());
// objectCard.removeAll();
objectCard.add( editor.initObjectNodeEditor(currentTuple, availProps.getSelectedItem()));
// ((CardLayout) cards.getLayout()).show( cards, ((ObjectDescriptor) currentTuple.get( "descr" )).getType().name() );
cards.repaint();
}
});
inner.add( availProps );
info.add( inner );
cards = new JPanel( new CardLayout() );
cards.add( new JLabel( "Select a node to edit" ), "NONE" );
cards.add( dataCard, ObjectDescriptor.NODETYPE.DATA.name() );
cards.add( objectCard, ObjectDescriptor.NODETYPE.OBJECT.name() );
dataCard.add( new JLabel( "DAT" ) );
objectCard.add(new JLabel("OBJ"));
mainPanel.add(info, BorderLayout.PAGE_START);
mainPanel.add(cards, BorderLayout.CENTER);
mainPanel.setVisible(true);
}
public void disable() {
currentNode.setText( "(n/a)" );
currentNode.setEnabled( false );
availProps.setModel( new DefaultComboBoxModel() );
availProps.setEnabled( false );
((CardLayout) cards.getLayout()).show( cards, "NONE" );
}
public void update( Tuple tuple ) {
currentTuple = tuple;
ObjectDescriptor od = (ObjectDescriptor) tuple.get( "descr" );
String label = (String) tuple.get( "label" );
currentNode.setEnabled( true );
currentNode.setText( label );
DefaultComboBoxModel model = new DefaultComboBoxModel();
if ( od != null ) {
for ( RelationDescriptor descr : od.getOutRelations() ) {
model.addElement( descr );
}
}
availProps.setModel( model );
availProps.setEnabled( true );
String type = od.getType().name();
((CardLayout) cards.getLayout()).show( cards, type );
if ( ObjectDescriptor.NODETYPE.DATA.name().equals(type) ) {
dataCard.removeAll();
dataCard.add( editor.initDataNodeEditor(tuple) );
} else if ( ObjectDescriptor.NODETYPE.OBJECT.name().equals(type) ) {
objectCard.removeAll();
objectCard.add( editor.initObjectNodeEditor( currentTuple, availProps.getSelectedItem() ) );
}
}
}
private Map<Object,Node> cache = new HashMap<Object, Node>();
private EditGUI gui;
private RelationDescriptor activeProp;
private Graph g;
public EditControlAdapter( Graph g ) {
this.g = g;
Table table = g.getNodeTable();
Iterator<Integer> iter = table.iterator();
while ( iter.hasNext() ) {
Integer index = iter.next();
Tuple tup = table.getTuple( index );
ObjectDescriptor od = ((ObjectDescriptor) tup.get( "descr" ));
if ( od != null ) {
Object o = od.getObject();
if ( o instanceof Thing ) {
cache.put( o, (Node) tup.get( "node" ) );
}
}
}
gui = new EditGUI( this );
}
@Override
public void itemClicked( VisualItem visualItem, MouseEvent mouseEvent ) {
if ( visualItem.getGroup().equals( Visualizer.nodes ) ) {
super.itemClicked( visualItem, mouseEvent );
if ( mouseEvent.getClickCount() >= 2 ) {
gui.update(visualItem);
// initNodeEditor( visualItem );
} else {
gui.disable();
}
} else {
gui.disable();
return;
}
}
private JComponent initDataNodeEditor( final Tuple tuple ) {
JPanel panel = new JPanel( new GridLayout( 2, 2 ) );
JComponent input;
JButton ok = new JButton( "Modify" );
JButton del = new JButton( "Remove" );
final ObjectDescriptor od = (ObjectDescriptor) tuple.get( "descr" );
final RelationDescriptor rd = od.getInRelations().get( 0 );
final Object oldValue = od.getObject();
Class valueType = od.getInRelations().get( 0 ).getRange();
if ( String.class.isAssignableFrom( valueType ) ) {
final JTextField text = new JTextField();
text.setText( (String) oldValue );
input = text;
ok.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
setValue( tuple, od, rd, oldValue, text.getText() );
gui.disable();
}
});
} else if ( Number.class.isAssignableFrom( valueType ) ) {
final JSpinner spin = new JSpinner();
if ( Long.class.isAssignableFrom( valueType ) ) {
SpinnerNumberModel num = new SpinnerNumberModel(
(Number) oldValue, Long.MIN_VALUE, Long.MAX_VALUE, 1 );
spin.setModel( num );
} else if ( Integer.class.isAssignableFrom( valueType ) ) {
SpinnerNumberModel num = new SpinnerNumberModel(
(Number) oldValue, Integer.MIN_VALUE, Integer.MAX_VALUE, 1 );
spin.setModel( num );
} else {
SpinnerNumberModel num = new SpinnerNumberModel(
(Number) oldValue, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 0.01 );
spin.setModel( num );
}
//TODO The others!!!!
input = spin;
ok.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
setValue( tuple, od, rd, oldValue, spin.getValue() );
gui.disable();
}
});
} else if ( Boolean.class.isAssignableFrom( valueType ) ) {
JRadioButton radio = new JRadioButton();
input = radio;
ok.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
if ( e.getActionCommand().equals( "Confirm" ) ) {
System.out.println( "Changing the value to !" );
}
}
});
} else if ( Date.class.isAssignableFrom( valueType ) ) {
final JSpinner spin = new JSpinner();
SpinnerDateModel dat = new SpinnerDateModel(
(Date) oldValue,
new Date(0),
null,
Calendar.HOUR );
spin.setModel( dat );
input = spin;
ok.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
setValue( tuple, od, rd, oldValue, spin.getValue() );
gui.disable();
}
});
} else {
throw new UnsupportedOperationException( "TODO -- Add support for " + valueType );
}
input.setPreferredSize( new Dimension( 250, 30 ) );
input.setMaximumSize( new Dimension( 500,30 ) );
del.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
VisualItem vis = (VisualItem) tuple;
Visualization graph = vis.getVisualization();
Node node = (Node) tuple.get( "node" );
Edge inArc = (Edge) node.inEdges().next();
AggregateTable att = (AggregateTable) graph.getGroup( Visualizer.AGGR );
synchronized ( att ) {
System.out.println( " Removing stuff " );
removeProperty( (RelationDescriptor) inArc.get( "descr"), inArc, inArc.getSourceNode(), graph );
System.out.println( " Removed stuff " );
}
}
});
panel.add( input );
panel.add( ok );
panel.add( del );
return panel;
}
private Component initObjectNodeEditor( final Tuple currentTuple, final Object selectedItem ) {
System.out.println(" Reset panel for avail Proprs " + selectedItem);
activeProp = (RelationDescriptor) selectedItem;
JPanel panel = new JPanel( new GridLayout( 2, 2 ) );
JButton remp = new JButton( "Remove Property" );
remp.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println( "Remove current tuple from " + currentTuple.get( "descr" ) + " prop " + activeProp );
VisualItem vis = (VisualItem) currentTuple;
Visualization graph = vis.getVisualization();
Node currNode = (Node) currentTuple.get( "node" );
Iterator<Edge> out = currNode.outEdges();
while ( out.hasNext() ) {
Edge edge = out.next();
RelationDescriptor rel = (RelationDescriptor) edge.get( "descr" );
if ( activeProp.equals( rel ) ) {
removeProperty( activeProp, edge, currNode, graph );
break;
}
}
gui.disable();
}
});
JButton remm = new JButton( "Remove Node" );
remm.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println( "Remove NODE " + currentTuple.get( "descr" ) );
VisualItem vis = (VisualItem) currentTuple;
Visualization graph = vis.getVisualization();
Node currNode = (Node) currentTuple.get( "node" );
Iterator<Edge> in = currNode.inEdges();
while ( in.hasNext() ) {
Edge edge = in.next();
removeProperty( (RelationDescriptor) edge.get( "descr" ), edge, currNode, graph );
}
gui.disable();
}
});
Box newP = new Box(BoxLayout.Y_AXIS);
newP.setBorder( BorderFactory.createTitledBorder( "Add new property" ) );
final JPanel inner = new JPanel( );
final JPanel deeper = new JPanel( new BorderLayout() );
final ObjectDescriptor od = (ObjectDescriptor) currentTuple.get( "descr" );
final JButton add = new JButton( "Add" );
final JComboBox propBox = new JComboBox();
DefaultComboBoxModel props = new DefaultComboBoxModel();
for ( String p : od.getRelations().keySet() ) {
props.addElement( p );
}
propBox.setModel( props );
propBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComponent dataEditor = prepareNewSelection(propBox.getSelectedItem(), od);
deeper.removeAll();
deeper.add(dataEditor, BorderLayout.CENTER);
inner.revalidate();
}
});
add.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
addRelation( (Node) currentTuple, od, od.getRelations().get( propBox.getSelectedItem() ), newValue );
gui.disable();
}
});
inner.add( propBox );
inner.add( deeper );
if ( propBox.getSelectedItem() != null ) {
deeper.add( prepareNewSelection( propBox.getSelectedItem(), od ), BorderLayout.CENTER );
}
inner.add( add );
newP.add( inner );
panel.add( remp );
panel.add( remm );
panel.add( newP );
return panel;
}
private void addRelation( Node root, ObjectDescriptor od, RelationDescriptor relationDescriptor, Object newValue ) {
boolean isObject = Thing.class.isAssignableFrom( newValue.getClass() );
System.out.println( "Adding on " + od + " using " + relationDescriptor + " setting value " + newValue );
// if single and already set, need to change the previous value!
if ( relationDescriptor.getSetter() != null ) {
Iterator<Edge> outs = root.outEdges();
while ( outs.hasNext() ) {
Edge out = outs.next();
RelationDescriptor prev = (RelationDescriptor) out.get( "descr" );
if ( prev.getProperty().equals( relationDescriptor.getProperty() ) ) {
try {
prev.getSetter().invoke( prev.getSubject(), newValue );
} catch (Exception e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
prev.setObject( newValue );
Node tgt = out.getTargetNode();
tgt.set( "label", newValue.toString() );
((ObjectDescriptor) tgt.get( "descr" )).setLabel(newValue.toString());
((ObjectDescriptor) tgt.get( "descr" )).setObject( newValue );
return;
}
}
}
RelationDescriptor rd = (RelationDescriptor) relationDescriptor.clone();
rd.setSubject( od.getObject() );
rd.setObject( newValue );
rd.setType( isObject ? RelationDescriptor.RELTYPE.OBJECT : RelationDescriptor.RELTYPE.DATA );
od.addOutRelation( rd );
try {
rd.getAdder().invoke( od.getObject(), newValue );
} catch (Exception e) {
e.printStackTrace();
}
VisualItem viz = (VisualItem) root;
AggregateTable agg = (AggregateTable) viz.getVisualization().getVisualGroup( Visualizer.AGGR );
Iterator<Tuple> nodes = g.getNodes().tuples();
while ( nodes.hasNext() ) {
Tuple n = nodes.next();
if ( n.canGet( "descr", ObjectDescriptor.class ) ) {
ObjectDescriptor ox = (ObjectDescriptor) n.get( "descr" );
if ( ox.getObject() == newValue ) {
Edge arc = g.addEdge( (Node) root.get( "node") , (Node) n.get( "node" ) );
arc.set( "label", rd.getProperty() );
arc.set( "iri", rd.getProperty() );
arc.set( "edge", arc );
arc.set( "descr", rd );
return;
}
}
}
synchronized ( agg ) {
Node target = g.addNode();
ObjectDescriptor td = new ObjectDescriptor();
td.addInRelation( rd );
td.setType( isObject ? ObjectDescriptor.NODETYPE.OBJECT : ObjectDescriptor.NODETYPE.DATA );
td.setObject( newValue );
if ( isObject ) {
td.setRelations( FactGraphAnalyzer.extractRelations( newValue ) );
}
int aggId = isObject? agg.getRowCount() : (Integer) root.get( "aggId" );
target.set( "aggId", aggId );
target.set( "label", newValue.toString() );
target.set( "node", target );
target.set( "descr", td );
target.set( "type", isObject ? ObjectDescriptor.NODETYPE.OBJECT : ObjectDescriptor.NODETYPE.DATA );
VisualItem tiz = viz.getVisualization().getVisualItem( Visualizer.nodes, target );
if ( isObject ) {
AggregateItem ai = (AggregateItem) agg.addItem();
ai.set( "id", aggId );
ai.addItem( tiz );
} else {
agg.addToAggregate( (Integer) root.get( "aggId" ), tiz );
}
Edge arc = g.addEdge( (Node) root.get( "node") , target );
arc.set( "label", rd.getProperty() );
arc.set( "iri", rd.getProperty() );
arc.set( "edge", arc );
arc.set( "descr", rd );
}
}
private Object newValue;
private JComponent prepareNewSelection( Object selectedItem, ObjectDescriptor od ) {
RelationDescriptor rd = od.getRelations().get( selectedItem );
JComponent input = new JLabel( "Not supported yet" );
if ( rd.getType().equals( RelationDescriptor.RELTYPE.DATA ) ) {
if ( String.class.isAssignableFrom( rd.getRange() ) ) {
final JTextField text = new JTextField();
input = text;
text.addFocusListener( new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
newValue = text.getText();
}
});
} else if ( Number.class.isAssignableFrom( rd.getRange() ) ) {
final JSpinner spin = new JSpinner();
if ( Long.class.isAssignableFrom( rd.getRange() ) ) {
SpinnerNumberModel num = new SpinnerNumberModel(
0, Long.MIN_VALUE, Long.MAX_VALUE, 1 );
spin.setModel( num );
} else if ( Integer.class.isAssignableFrom( rd.getRange() ) ) {
SpinnerNumberModel num = new SpinnerNumberModel(
0, Integer.MIN_VALUE, Integer.MAX_VALUE, 1 );
spin.setModel( num );
} else {
SpinnerNumberModel num = new SpinnerNumberModel(
0.0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 0.01 );
spin.setModel( num );
}
input = spin;
spin.addChangeListener( new ChangeListener() {
public void stateChanged(ChangeEvent e) {
newValue = spin.getValue();
}
});
} else if ( Date.class.isAssignableFrom( rd.getRange() ) ) {
final JSpinner spin = new JSpinner();
SpinnerDateModel dat = new SpinnerDateModel(
new Date(),
new Date(0),
null,
Calendar.HOUR );
spin.setModel( dat );
input = spin;
spin.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
newValue = spin.getValue();
}
});
}
input.setPreferredSize( new Dimension( 200, 30 ) );
input.addVetoableChangeListener( new VetoableChangeListener() {
public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
// TODO Restrictions might be placed here
}
});
} else {
final CardLayout cardLayout = new CardLayout();
final JPanel chooser = new JPanel( );
final JPanel selector = new JPanel( cardLayout );
JRadioButton newOb = new JRadioButton( "New Obj", true );
newOb.addItemListener( new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if ( e.getStateChange() == ItemEvent.SELECTED ) {
cardLayout.show( selector, ((JRadioButton) e.getItem()).getText());
}
}
});
JRadioButton oldOb = new JRadioButton( "Existing", false );
oldOb.addItemListener( new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if ( e.getStateChange() == ItemEvent.SELECTED ) {
cardLayout.show( selector, ((JRadioButton) e.getItem()).getText());
}
}
});
ButtonGroup bg = new ButtonGroup();
bg.add( newOb );
bg.add( oldOb );
final JComboBox objTypes = listCompatibleTypes( rd.getRange() );
objTypes.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
Class k = Class.forName((String) objTypes.getSelectedItem() + "Impl");
newValue = k.getConstructor().newInstance();
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
selector.add( objTypes, newOb.getText() );
try {
Class k = Class.forName((String) objTypes.getSelectedItem() + "Impl");
newValue = k.getConstructor().newInstance();
} catch (Exception ex) {
ex.printStackTrace();
}
final JComboBox oldObjs = getPossibleTargets(rd.getRange(), g);
oldObjs.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
newValue = oldObjs.getSelectedItem();
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
selector.add( oldObjs, oldOb.getText() );
try {
newValue = oldObjs.getModel().getElementAt( 0 );
} catch (Exception ex) {
ex.printStackTrace();
}
chooser.add( newOb );
chooser.add( oldOb );
chooser.add( selector );
input = chooser;
}
return input;
}
private JComboBox getPossibleTargets(Class range, Graph g) {
JComboBox box = new JComboBox();
DefaultComboBoxModel model = new DefaultComboBoxModel();
Iterator<Tuple> nodes = g.getNodes().tuples();
while ( nodes.hasNext() ) {
Tuple n = nodes.next();
if ( n.canGet( "descr", ObjectDescriptor.class ) ) {
ObjectDescriptor od = (ObjectDescriptor) n.get( "descr" );
if ( od.getObject() != null && range.isAssignableFrom( od.getObject().getClass() ) ) {
model.addElement( od.getObject() );
}
}
}
box.setModel( model );
return box;
}
private JComboBox listCompatibleTypes(Class range) {
JComboBox box = new JComboBox();
DefaultComboBoxModel model = new DefaultComboBoxModel();
//TODO: cheating here...
// Class shadow = null;
// try {
// shadow = Class.forName( range.getName() + "$$Shadow" );
// } catch (ClassNotFoundException e) {
// e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
// }
// addSubType( shadow, model);
box.setModel( model );
return box;
}
private void addSubType(Class ix, DefaultComboBoxModel subs) {
// if ( ix.getName().endsWith( "$$Shadow") ) {
// subs.addElement(ix.getName().replace("$$Shadow", ""));
// for ( Class sx : ix.getInterfaces() ) {
// addSubType( sx, subs );
// }
// }
}
private void removeProperty( RelationDescriptor activeProp, Edge edge, Node currNode, Visualization graph ) {
AggregateTable agg = (AggregateTable) graph.getVisualGroup( Visualizer.AGGR );
synchronized (agg) {
RelationDescriptor rel = (RelationDescriptor) edge.get( "descr" );
try {
activeProp.getRemover().invoke( activeProp.getSubject(), activeProp.getObject() );
} catch ( Exception ex ) {
ex.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
((ObjectDescriptor) currNode.get( "descr" )).getOutRelations().remove( rel );
System.out.println( "After removing " + ((ObjectDescriptor) currNode.get( "descr" )).getOutRelations() );
Node target = edge.getTargetNode();
graph.getSourceData( Visualizer.edges ).removeTuple( (Edge) edge.get("edge") );
if ( target.getInDegree() == 0 ) {
System.out.println( "NEed to remove orphan node " + target.get( "label" ) );
removeCascade( target, graph, agg );
} else {
((ObjectDescriptor) target.get( "descr" )).getInRelations().remove( rel );
}
}
}
private void removeCascade( Node target, Visualization viz, AggregateTable att ) {
synchronized ( att ) {
Iterator<Edge> outs = target.outEdges();
Collection<Edge> victims = new ArrayList<Edge>();
VisualItem visualNode = viz.getVisualItem( Visualizer.nodes, target );
while ( outs.hasNext() ) {
victims.add( outs.next() );
}
for ( Edge out : victims ) {
Node x = out.getTargetNode();
viz.getSourceData( Visualizer.edges ).removeTuple( out );
if ( x.getInDegree() < 1 ) {
removeCascade( x, viz, att );
}
}
if ( target.get( "aggId" ) != null ) {
att.removeFromAggregate( (Integer) target.get( "aggId" ), visualNode );
if ( ObjectDescriptor.NODETYPE.OBJECT.equals( target.get( "type" ) ) ) {
att.removeRow( (Integer) target.get( "aggId" ) );
}
}
System.out.println( "Finally remove orphan node " + target.get( "label" ) );
viz.getSourceData( Visualizer.nodes ).removeTuple( (Node) target.get( "node" ) );
}
}
private synchronized void setValue( Tuple tuple, ObjectDescriptor target, RelationDescriptor descr, Object oldValue, Object newValue ) {
try {
Object obj = descr.getSubject();
Method setter = descr.getSetter();
if ( setter != null ) {
setter.invoke(obj, newValue);
} else {
descr.getRemover().invoke(obj, oldValue);
descr.getAdder().invoke(obj, newValue);
}
target.setObject(newValue);
target.setLabel(newValue.toString());
tuple.set( "label", newValue.toString() );
} catch ( Exception exc ) {
exc.printStackTrace();
}
}
public JPanel getPanel() {
return gui.getPanel();
}
}