/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* BeanConnection.java
* Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
*
*/
package weka.gui.beans;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.BeanInfo;
import java.beans.EventSetDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Vector;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingConstants;
/**
* Class for encapsulating a connection between two beans. Also
* maintains a list of all connections
*
* @author Mark Hall
* @version $Revision: 5611 $
*/
public class BeanConnection
implements Serializable {
/** for serialization */
private static final long serialVersionUID = 8804264241791332064L;
/**
* The list of connections
*/
public static Vector CONNECTIONS = new Vector();
// details for this connection
private BeanInstance m_source;
private BeanInstance m_target;
/**
* The name of the event for this connection
*/
private String m_eventName;
// Should the connection be painted?
private boolean m_hidden = false;
/**
* Reset the list of connections
*/
public static void reset() {
CONNECTIONS = new Vector();
}
/**
* Returns the list of connections
*
* @return the list of connections
*/
public static Vector getConnections() {
return CONNECTIONS;
}
/**
* Describe setConnections
method here.
*
* @param connections a Vector
value
*/
public static void setConnections(Vector connections) {
CONNECTIONS = connections;
}
/**
* Returns true if there is a link between the supplied source and
* target BeanInstances at an earlier index than the supplied index
*
* @param source the source BeanInstance
* @param target the target BeanInstance
* @param index the index to compare to
* @return true if there is already a link at an earlier index
*/
private static boolean previousLink(BeanInstance source, BeanInstance target,
int index) {
for (int i = 0; i < CONNECTIONS.size(); i++) {
BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
BeanInstance compSource = bc.getSource();
BeanInstance compTarget = bc.getTarget();
if (compSource == source && compTarget == target && index < i) {
return true;
}
}
return false;
}
/**
* A candidate BeanInstance can be an input if it is in the listToCheck
* and it is the source of a connection to a target that is in the
* listToCheck
*/
private static boolean checkForSource(BeanInstance candidate,
Vector listToCheck) {
for (int i = 0; i < CONNECTIONS.size(); i++) {
BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
if (bc.getSource() != candidate) {
continue;
}
// check to see if target is in list
for (int j = 0; j < listToCheck.size(); j++) {
BeanInstance tempTarget = (BeanInstance)listToCheck.elementAt(j);
if (bc.getTarget() == tempTarget) {
return true;
}
}
}
return false;
}
/**
* A candidate BeanInstance can't be an input if it is the target
* of a connection from a source that is in the listToCheck
*/
private static boolean checkTargetConstraint(BeanInstance candidate,
Vector listToCheck) {
for (int i = 0; i < CONNECTIONS.size(); i++) {
BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
if (bc.getTarget() == candidate) {
for (int j = 0; j < listToCheck.size(); j++) {
BeanInstance tempSource = (BeanInstance)listToCheck.elementAt(j);
if (bc.getSource() == tempSource) {
return false;
}
}
}
}
return true;
}
/**
* Returns a vector of BeanConnections associated with
* the supplied vector of BeanInstances, i.e. all connections
* that exist between those BeanInstances in the subFlow.
*
* @param subFlow a Vector of BeanInstances
* @return a Vector of BeanConnections
*/
public static Vector associatedConnections(Vector subFlow) {
Vector associatedConnections = new Vector();
for (int i = 0; i < CONNECTIONS.size(); i++) {
BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
BeanInstance tempSource = bc.getSource();
BeanInstance tempTarget = bc.getTarget();
boolean sourceInSubFlow = false;
boolean targetInSubFlow = false;
for (int j = 0; j < subFlow.size(); j++) {
BeanInstance toCheck = (BeanInstance)subFlow.elementAt(j);
if (toCheck == tempSource) {
sourceInSubFlow = true;
}
if (toCheck == tempTarget) {
targetInSubFlow = true;
}
if (sourceInSubFlow && targetInSubFlow) {
associatedConnections.add(bc);
break;
}
}
}
return associatedConnections;
}
/**
* Returns a vector of BeanInstances that can be considered
* as inputs (or the left-hand side of a sub-flow)
*
* @param subset the sub-flow to examine
* @return a Vector of inputs to the sub-flow
*/
public static Vector inputs(Vector subset) {
Vector result = new Vector();
for (int i = 0; i < subset.size(); i++) {
BeanInstance temp = (BeanInstance)subset.elementAt(i);
// if (checkForSource(temp, subset)) {
// now check target constraint
if (checkTargetConstraint(temp, subset)) {
result.add(temp);
}
// }
}
return result;
}
/**
* A candidate BeanInstance can be an output if it is in the listToCheck
* and it is the target of a connection from a source that is in the
* listToCheck
*/
private static boolean checkForTarget(BeanInstance candidate,
Vector listToCheck) {
for (int i = 0; i < CONNECTIONS.size(); i++) {
BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
if (bc.getTarget() != candidate) {
continue;
}
// check to see if source is in list
for (int j = 0; j < listToCheck.size(); j++) {
BeanInstance tempSource = (BeanInstance)listToCheck.elementAt(j);
if (bc.getSource() == tempSource) {
return true;
}
}
}
return false;
}
private static boolean isInList(BeanInstance candidate,
Vector listToCheck) {
for (int i = 0; i < listToCheck.size(); i++) {
BeanInstance temp = (BeanInstance)listToCheck.elementAt(i);
if (candidate == temp) {
return true;
}
}
return false;
}
/**
* A candidate BeanInstance can't be an output if it is the source
* of a connection only to target(s) that are in the listToCheck
*/
private static boolean checkSourceConstraint(BeanInstance candidate,
Vector listToCheck) {
boolean result = true;
for (int i = 0; i < CONNECTIONS.size(); i++) {
BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
if (bc.getSource() == candidate) {
BeanInstance cTarget = bc.getTarget();
// is the target of this connection external to the list to check?
if (!isInList(cTarget, listToCheck)) {
return true;
}
for (int j = 0; j < listToCheck.size(); j++) {
BeanInstance tempTarget = (BeanInstance)listToCheck.elementAt(j);
if (bc.getTarget() == tempTarget) {
result = false;
}
}
}
}
return result;
}
/**
* Returns a vector of BeanInstances that can be considered
* as outputs (or the right-hand side of a sub-flow)
*
* @param subset the sub-flow to examine
* @return a Vector of outputs of the sub-flow
*/
public static Vector outputs(Vector subset) {
Vector result = new Vector();
for (int i = 0; i < subset.size(); i++) {
BeanInstance temp = (BeanInstance)subset.elementAt(i);
if (checkForTarget(temp, subset)) {
// now check source constraint
if (checkSourceConstraint(temp, subset)) {
// now check that this bean can actually produce some events
try {
BeanInfo bi = Introspector.getBeanInfo(temp.getBean().getClass());
EventSetDescriptor [] esd = bi.getEventSetDescriptors();
if (esd != null && esd.length > 0) {
result.add(temp);
}
} catch (IntrospectionException ex) {
// quietly ignore
}
}
}
}
return result;
}
/**
* Renders the connections and their names on the supplied graphics
* context
*
* @param gx a Graphics
value
*/
public static void paintConnections(Graphics gx) {
for (int i = 0; i < CONNECTIONS.size(); i++) {
BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
if (!bc.isHidden()) {
BeanInstance source = bc.getSource();
BeanInstance target = bc.getTarget();
EventSetDescriptor srcEsd = bc.getSourceEventSetDescriptor();
BeanVisual sourceVisual = (source.getBean() instanceof Visible) ?
((Visible)source.getBean()).getVisual() :
null;
BeanVisual targetVisual = (target.getBean() instanceof Visible) ?
((Visible)target.getBean()).getVisual() :
null;
if (sourceVisual != null && targetVisual != null) {
Point bestSourcePt =
sourceVisual.getClosestConnectorPoint(
new Point((target.getX()+(target.getWidth()/2)),
(target.getY() + (target.getHeight() / 2))));
Point bestTargetPt =
targetVisual.getClosestConnectorPoint(
new Point((source.getX()+(source.getWidth()/2)),
(source.getY() + (source.getHeight() / 2))));
gx.setColor(Color.red);
boolean active = true;
if (source.getBean() instanceof EventConstraints) {
if (!((EventConstraints) source.getBean()).
eventGeneratable(srcEsd.getName())) {
gx.setColor(Color.gray); // link not active at this time
active = false;
}
}
gx.drawLine((int)bestSourcePt.getX(), (int)bestSourcePt.getY(),
(int)bestTargetPt.getX(), (int)bestTargetPt.getY());
// paint an arrow head
double angle;
try {
double a =
(double)(bestSourcePt.getY() -
bestTargetPt.getY()) /
(double)(bestSourcePt.getX() - bestTargetPt.getX());
angle = Math.atan(a);
} catch(Exception ex) {
angle = Math.PI / 2;
}
// Point arrowstart = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
Point arrowstart = new Point(bestTargetPt.x,
bestTargetPt.y);
Point arrowoffset = new Point((int)(7 * Math.cos(angle)),
(int)(7 * Math.sin(angle)));
Point arrowend;
if (bestSourcePt.getX() >= bestTargetPt.getX()) {
arrowend = new Point(arrowstart.x + arrowoffset.x,
arrowstart.y + arrowoffset.y);
} else {
arrowend = new Point(arrowstart.x - arrowoffset.x,
arrowstart.y - arrowoffset.y);
}
int xs[] = { arrowstart.x,
arrowend.x + (int)(7 * Math.cos(angle + (Math.PI / 2))),
arrowend.x + (int)(7 * Math.cos(angle - (Math.PI / 2)))};
int ys[] = { arrowstart.y,
arrowend.y + (int)(7 * Math.sin(angle + (Math.PI / 2))),
arrowend.y + (int)(7 * Math.sin(angle - (Math.PI / 2)))};
gx.fillPolygon(xs, ys, 3);
// ----
// paint the connection name
int midx = (int)bestSourcePt.getX();
midx += (int)((bestTargetPt.getX() - bestSourcePt.getX()) / 2);
int midy = (int)bestSourcePt.getY();
midy += (int)((bestTargetPt.getY() - bestSourcePt.getY()) / 2) - 2 ;
gx.setColor((active) ? Color.blue : Color.gray);
if (previousLink(source, target, i)) {
midy -= 15;
}
gx.drawString(srcEsd.getName(), midx, midy);
}
}
}
}
/**
* Return a list of connections within some delta of a point
*
* @param pt the point at which to look for connections
* @param delta connections have to be within this delta of the point
* @return a list of connections
*/
public static Vector getClosestConnections(Point pt, int delta) {
Vector closestConnections = new Vector();
for (int i = 0; i < CONNECTIONS.size(); i++) {
BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
BeanInstance source = bc.getSource();
BeanInstance target = bc.getTarget();
EventSetDescriptor srcEsd = bc.getSourceEventSetDescriptor();
BeanVisual sourceVisual = (source.getBean() instanceof Visible) ?
((Visible)source.getBean()).getVisual() :
null;
BeanVisual targetVisual = (target.getBean() instanceof Visible) ?
((Visible)target.getBean()).getVisual() :
null;
if (sourceVisual != null && targetVisual != null) {
Point bestSourcePt =
sourceVisual.getClosestConnectorPoint(
new Point((target.getX()+(target.getWidth()/2)),
(target.getY() + (target.getHeight() / 2))));
Point bestTargetPt =
targetVisual.getClosestConnectorPoint(
new Point((source.getX()+(source.getWidth()/2)),
(source.getY() + (source.getHeight() / 2))));
int minx = (int) Math.min(bestSourcePt.getX(), bestTargetPt.getX());
int maxx = (int) Math.max(bestSourcePt.getX(), bestTargetPt.getX());
int miny = (int) Math.min(bestSourcePt.getY(), bestTargetPt.getY());
int maxy = (int) Math.max(bestSourcePt.getY(), bestTargetPt.getY());
// check to see if supplied pt is inside bounding box
if (pt.getX() >= minx-delta && pt.getX() <= maxx+delta &&
pt.getY() >= miny-delta && pt.getY() <= maxy+delta) {
// now see if the point is within delta of the line
// formulate ax + by + c = 0
double a = bestSourcePt.getY() - bestTargetPt.getY();
double b = bestTargetPt.getX() - bestSourcePt.getX();
double c = (bestSourcePt.getX() * bestTargetPt.getY()) -
(bestTargetPt.getX() * bestSourcePt.getY());
double distance = Math.abs((a * pt.getX()) + (b * pt.getY()) + c);
distance /= Math.abs(Math.sqrt((a*a) + (b*b)));
if (distance <= delta) {
closestConnections.addElement(bc);
}
}
}
}
return closestConnections;
}
/**
* Remove all connections for a bean. If the bean is a target for
* receiving events then it gets deregistered from the corresonding
* source bean. If the bean is a source of events then all targets
* implementing BeanCommon are notified via their
* disconnectionNotification methods that the source (and hence the
* connection) is going away.
*
* @param instance the bean to remove connections to/from
*/
public static void removeConnections(BeanInstance instance) {
Vector instancesToRemoveFor = new Vector();
if (instance.getBean() instanceof MetaBean) {
instancesToRemoveFor =
((MetaBean)instance.getBean()).getBeansInSubFlow();
} else {
instancesToRemoveFor.add(instance);
}
Vector removeVector = new Vector();
for (int j = 0; j < instancesToRemoveFor.size(); j++) {
BeanInstance tempInstance =
(BeanInstance)instancesToRemoveFor.elementAt(j);
for (int i = 0; i < CONNECTIONS.size(); i++) {
// In cases where this instance is the target, deregister it
// as a listener for the source
BeanConnection bc = (BeanConnection)CONNECTIONS.elementAt(i);
BeanInstance tempTarget = bc.getTarget();
BeanInstance tempSource = bc.getSource();
EventSetDescriptor tempEsd = bc.getSourceEventSetDescriptor();
if (tempInstance == tempTarget) {
// try to deregister the target as a listener for the source
try {
Method deregisterMethod = tempEsd.getRemoveListenerMethod();
Object targetBean = tempTarget.getBean();
Object [] args = new Object[1];
args[0] = targetBean;
deregisterMethod.invoke(tempSource.getBean(), args);
// System.err.println("Deregistering listener");
removeVector.addElement(bc);
} catch (Exception ex) {
ex.printStackTrace();
}
} else if (tempInstance == tempSource) {
removeVector.addElement(bc);
if (tempTarget.getBean() instanceof BeanCommon) {
// tell the target that the source is going away, therefore
// this type of connection is as well
((BeanCommon)tempTarget.getBean()).
disconnectionNotification(tempEsd.getName(),
tempSource.getBean());
}
}
}
}
for (int i = 0; i < removeVector.size(); i++) {
// System.err.println("removing connection");
CONNECTIONS.removeElement((BeanConnection)removeVector.elementAt(i));
}
}
public static void doMetaConnection(BeanInstance source, BeanInstance target,
final EventSetDescriptor esd,
final JComponent displayComponent) {
Object targetBean = target.getBean();
BeanInstance realTarget = null;
final BeanInstance realSource = source;
if (targetBean instanceof MetaBean) {
Vector receivers = ((MetaBean)targetBean).getSuitableTargets(esd);
if (receivers.size() == 1) {
realTarget = (BeanInstance)receivers.elementAt(0);
BeanConnection bc = new BeanConnection(realSource, realTarget,
esd);
// m_target = (BeanInstance)receivers.elementAt(0);
} else {
// have to do the popup thing here
int menuItemCount = 0;
JPopupMenu targetConnectionMenu = new JPopupMenu();
targetConnectionMenu.insert(new JLabel("Select target",
SwingConstants.CENTER),
menuItemCount++);
for (int i = 0; i < receivers.size(); i++) {
final BeanInstance tempTarget =
(BeanInstance)receivers.elementAt(i);
String tName = ""+(i+1)+": "
+ ((tempTarget.getBean() instanceof BeanCommon)
? ((BeanCommon)tempTarget.getBean()).getCustomName()
: tempTarget.getBean().getClass().getName());
JMenuItem targetItem = new JMenuItem(tName);
targetItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// finalTarget.add(tempTarget);
BeanConnection bc =
new BeanConnection(realSource, tempTarget,
esd);
displayComponent.repaint();
}
});
targetConnectionMenu.add(targetItem);
menuItemCount++;
}
targetConnectionMenu.show(displayComponent, target.getX(),
target.getY());
// m_target = (BeanInstance)finalTarget.elementAt(0);
}
}
}
/**
* Creates a new BeanConnection
instance.
*
* @param source the source bean
* @param target the target bean
* @param esd the EventSetDescriptor for the connection
* be displayed
*/
public BeanConnection(BeanInstance source, BeanInstance target,
EventSetDescriptor esd) {
m_source = source;
m_target = target;
// m_sourceEsd = sourceEsd;
m_eventName = esd.getName();
// System.err.println(m_eventName);
// attempt to connect source and target beans
Method registrationMethod =
// m_sourceEsd.getAddListenerMethod();
// getSourceEventSetDescriptor().getAddListenerMethod();
esd.getAddListenerMethod();
Object targetBean = m_target.getBean();
Object [] args = new Object[1];
args[0] = targetBean;
Class listenerClass = esd.getListenerType();
if (listenerClass.isInstance(targetBean)) {
try {
registrationMethod.invoke(m_source.getBean(), args);
// if the target implements BeanCommon, then inform
// it that it has been registered as a listener with a source via
// the named listener interface
if (targetBean instanceof BeanCommon) {
((BeanCommon)targetBean).
connectionNotification(esd.getName(), m_source.getBean());
}
CONNECTIONS.addElement(this);
} catch (Exception ex) {
System.err.println("[BeanConnection] Unable to connect beans");
ex.printStackTrace();
}
} else {
System.err.println("[BeanConnection] Unable to connect beans");
}
}
/**
* Make this connection invisible on the display
*
* @param hidden true to make the connection invisible
*/
public void setHidden(boolean hidden) {
m_hidden = hidden;
}
/**
* Returns true if this connection is invisible
*
* @return true if connection is invisible
*/
public boolean isHidden() {
return m_hidden;
}
/**
* Remove this connection
*/
public void remove() {
EventSetDescriptor tempEsd = getSourceEventSetDescriptor();
// try to deregister the target as a listener for the source
try {
Method deregisterMethod = tempEsd.getRemoveListenerMethod();
Object targetBean = getTarget().getBean();
Object [] args = new Object[1];
args[0] = targetBean;
deregisterMethod.invoke(getSource().getBean(), args);
// System.err.println("Deregistering listener");
} catch (Exception ex) {
ex.printStackTrace();
}
if (getTarget().getBean() instanceof BeanCommon) {
// tell the target that this connection is going away
((BeanCommon)getTarget().getBean()).
disconnectionNotification(tempEsd.getName(),
getSource().getBean());
}
CONNECTIONS.remove(this);
}
/**
* returns the source BeanInstance for this connection
*
* @return a BeanInstance
value
*/
public BeanInstance getSource() {
return m_source;
}
/**
* Returns the target BeanInstance for this connection
*
* @return a BeanInstance
value
*/
public BeanInstance getTarget() {
return m_target;
}
/**
* Returns the name of the event for this conncetion
*
* @return the name of the event for this connection
*/
public String getEventName() {
return m_eventName;
}
/**
* Returns the event set descriptor for the event generated by the source
* for this connection
*
* @return an EventSetDescriptor
value
*/
protected EventSetDescriptor getSourceEventSetDescriptor() {
JComponent bc = (JComponent)m_source.getBean();
try {
BeanInfo sourceInfo = Introspector.getBeanInfo(bc.getClass());
if (sourceInfo == null) {
System.err.println("[BeanConnection] Error getting bean info, source info is null.");
} else {
EventSetDescriptor [] esds = sourceInfo.getEventSetDescriptors();
for (int i = 0; i < esds.length; i++) {
if (esds[i].getName().compareTo(m_eventName) == 0) {
return esds[i];
}
}
}
} catch (Exception ex) {
System.err.println("[BeanConnection] Problem retrieving event set descriptor");
}
return null;
// return m_sourceEsd;
}
}