package jadex.tools.convcenter; import jadex.base.fipa.FIPAMessageType; import jadex.base.fipa.SFipa; import jadex.bdi.runtime.AgentEvent; import jadex.bdi.runtime.IBDIExternalAccess; import jadex.bdi.runtime.IBDIInternalAccess; import jadex.bdi.runtime.IMessageEvent; import jadex.bdi.runtime.IMessageEventListener; import jadex.bridge.ContentException; import jadex.bridge.IComponentStep; import jadex.bridge.IInternalAccess; import jadex.bridge.MessageType; import jadex.bridge.MessageType.ParameterSpecification; import jadex.commons.Properties; import jadex.commons.Property; import jadex.commons.SGUI; import jadex.commons.SUtil; import jadex.xml.bean.JavaReader; import jadex.xml.bean.JavaWriter; import java.awt.BorderLayout; import java.awt.Component; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; import javax.swing.UIDefaults; import javax.swing.border.EtchedBorder; import javax.swing.border.TitledBorder; /** * A panel for sending and receiving messages. */ public class FipaConversationPanel extends JSplitPane { //-------- static part -------- /** * The image icons. */ protected static UIDefaults icons = new UIDefaults(new Object[] { // Tab icons. "new_message", SGUI.makeIcon(FipaConversationPanel.class, "/jadex/tools/common/images/new_new_message.png"), "sent_message", SGUI.makeIcon(FipaConversationPanel.class, "/jadex/tools/common/images/new_sent_message.png"), "received_message", SGUI.makeIcon(FipaConversationPanel.class, "/jadex/tools/common/images/new_received_message.png") }); //-------- attributes -------- /** The agent to dispatch events to. */ protected IBDIExternalAccess agent; /** The tabbed panel. */ protected JTabbedPane tabs; /** The send message panel. */ protected FipaMessagePanel sendpanel; /** The list of sent messages. */ protected JList sentmsgs; /** The list of received messages. */ protected JList receivedmsgs; /** Registered message events. */ protected List regmsgs; //-------- constructors -------- /** * Create the gui. */ public FipaConversationPanel(final IBDIExternalAccess agent, Component comptree) { super(JSplitPane.HORIZONTAL_SPLIT, true); setOneTouchExpandable(true); this.agent = agent; this.regmsgs = new ArrayList(); // Right side starts with initial send panel only. Map msg = new HashMap(); msg.put(SFipa.SENDER, agent.getComponentIdentifier()); sendpanel = new FipaMessagePanel(msg, agent.getServiceProvider(), comptree); JButton send = new JButton("Send"); send.setToolTipText("Send the specified message"); send.putClientProperty(SGUI.AUTO_ADJUST, Boolean.TRUE); send.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { // Hack! For handling conversations. // If no replies are sent sentmessages are Map msg = sendpanel.getMessage(); MessageType mt = new FIPAMessageType(); // (MessageType)msg.get(ConversationPlugin.ENCODED_MESSAGE_TYPE); String ri = mt.getReceiverIdentifier(); ParameterSpecification ris = mt.getParameter(ri); // Check if receiver is specified if(ris.isSet()) { Object value = msg.get(ri); if(value==null || value instanceof Object[] && ((Object[])value).length==0) // Hack!!! Even for set may use single cid??? { noReceiverSpecified(); } else { sendMessage(msg); } } else { if(msg.get(ri)==null) { noReceiverSpecified(); } else { sendMessage(msg); } } } }); JButton reset = new JButton("Reset"); reset.setToolTipText("Reset all specified message values"); reset.putClientProperty(SGUI.AUTO_ADJUST, Boolean.TRUE); reset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { resetMessage(); } }); JPanel sendcont = new JPanel(new BorderLayout()); sendcont.add(BorderLayout.CENTER, sendpanel); JPanel south = new JPanel(new FlowLayout(FlowLayout.RIGHT)); south.add(send); south.add(reset); // HelpBroker hb = SHelp.setupHelp(FipaConversationPanel.this, "tools.conversationcenter"); // if(hb != null) // { // JButton help = new JButton("Help"); // help.setToolTipText("Open the Javahelp for the Conversation Center"); // help.putClientProperty(SGUI.AUTO_ADJUST, Boolean.TRUE); // help.addActionListener(new CSH.DisplayHelpFromSource(hb)); // south.add(help); // } sendcont.add(BorderLayout.SOUTH, south); final JScrollPane sendtab = new JScrollPane(sendcont); sendtab.setBorder(null); // Left side contains lists of sent/received messages. JPanel lists = new JPanel(new GridBagLayout()); GridBagConstraints gbcons = new GridBagConstraints(0, 0, GridBagConstraints.REMAINDER, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(2,2,2,2), 0, 0); sentmsgs = new JList(new DefaultListModel()); sentmsgs.setCellRenderer(new MessageListCellRenderer()); sentmsgs.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if(e.getClickCount()==2 && sentmsgs.locationToIndex(e.getPoint())!=-1) { final Map msg = (Map)sentmsgs.getModel() .getElementAt(sentmsgs.locationToIndex(e.getPoint())); final JPanel msgtab = new JPanel(new BorderLayout()); final FipaMessagePanel msgpanel = new FipaMessagePanel(msg, agent.getServiceProvider(), null); msgpanel.setEditable(false); final JScrollPane scroll = new JScrollPane(msgtab); scroll.setBorder(null); JButton edit = new JButton("Edit"); edit.setToolTipText("Edit this sent message"); edit.putClientProperty(SGUI.AUTO_ADJUST, Boolean.TRUE); edit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { sendpanel.setMessage(cloneMessage(msg)); tabs.setSelectedComponent(sendtab); } }); JButton send = new JButton("Resend"); send.setMargin(new Insets(2,2,2,2)); send.setToolTipText("Send this message again"); send.putClientProperty(SGUI.AUTO_ADJUST, Boolean.TRUE); send.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { sendMessage(msg); } }); JButton reset = new JButton("Close"); reset.setToolTipText("Close displayed message"); reset.putClientProperty(SGUI.AUTO_ADJUST, Boolean.TRUE); reset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { tabs.remove(scroll); } }); msgtab.add(BorderLayout.CENTER, msgpanel); JPanel south = new JPanel(new FlowLayout(FlowLayout.RIGHT)); south.add(edit); south.add(send); south.add(reset); msgtab.add(BorderLayout.SOUTH, south); tabs.addTab(getMessageTitle(msg), icons.getIcon("sent_message"), scroll); tabs.setSelectedComponent(scroll); SGUI.adjustComponentSizes(FipaConversationPanel.this); } } }); JPanel cpane = new JPanel(new BorderLayout()); cpane.add(BorderLayout.CENTER, new JScrollPane(sentmsgs)); cpane.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED), " Sent Messages ")); lists.add(cpane, gbcons); gbcons.gridy++; receivedmsgs = new JList(new DefaultListModel()); receivedmsgs.setCellRenderer(new MessageListCellRenderer()); receivedmsgs.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if(e.getClickCount()==2) { int idx = receivedmsgs.locationToIndex(e.getPoint()); if(idx!=-1) { final Map msg = (Map)receivedmsgs.getModel().getElementAt(idx); final JPanel msgtab = new JPanel(new BorderLayout()); final FipaMessagePanel msgpanel = new FipaMessagePanel(msg, agent.getServiceProvider(), null); msgpanel.setEditable(false); final JScrollPane scroll = new JScrollPane(msgtab); scroll.setBorder(null); JButton reply = new JButton("Reply"); reply.setToolTipText("Set up a reply message"); reply.putClientProperty(SGUI.AUTO_ADJUST, Boolean.TRUE); reply.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { agent.scheduleStep(new IComponentStep() { public Object execute(IInternalAccess ia) { IBDIInternalAccess scope = (IBDIInternalAccess)ia; IMessageEvent me = createMessageEvent(scope, msg); IMessageEvent reply = scope.getEventbase().createReply(me, "fipamsg"); final Map replymsg = createMessageMap(scope, reply); SwingUtilities.invokeLater(new Runnable() { public void run() { replymsg.put(SFipa.SENDER, agent.getComponentIdentifier()); sendpanel.setMessage(replymsg); tabs.setSelectedComponent(sendtab); } }); return null; } }); } }); JButton reset = new JButton("Close"); reset.setToolTipText("Close this message view"); reset.putClientProperty(SGUI.AUTO_ADJUST, Boolean.TRUE); reset.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { tabs.remove(scroll); } }); msgtab.add(BorderLayout.CENTER, msgpanel); JPanel south = new JPanel(new FlowLayout(FlowLayout.RIGHT)); south.add(reply); south.add(reset); msgtab.add(BorderLayout.SOUTH, south); tabs.addTab(getMessageTitle(msg), icons.getIcon("received_message"), scroll); tabs.setSelectedComponent(scroll); SGUI.adjustComponentSizes(FipaConversationPanel.this); } } } }); cpane = new JPanel(new BorderLayout()); cpane.add(BorderLayout.CENTER, new JScrollPane(receivedmsgs)); cpane.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED), " Received Messages ")); lists.add(cpane, gbcons); gbcons.gridy++; gbcons.weighty = 0; cpane = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JButton clear = new JButton("Clear"); clear.setToolTipText("Clear the lists of sent and received messages"); clear.putClientProperty(SGUI.AUTO_ADJUST, Boolean.TRUE); clear.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final IMessageEvent[] msgs = (IMessageEvent[])regmsgs.toArray(new IMessageEvent[regmsgs.size()]); agent.scheduleStep(new IComponentStep() { public Object execute(IInternalAccess ia) { for(int i=0; i<msgs.length; i++) { IBDIInternalAccess scope = (IBDIInternalAccess)ia; scope.getEventbase().deregisterMessageEvent(msgs[i]); } return null; } }); regmsgs.clear(); ((DefaultListModel)sentmsgs.getModel()).removeAllElements(); ((DefaultListModel)receivedmsgs.getModel()).removeAllElements(); while(tabs.getComponentCount()>1) tabs.remove(1); } }); cpane.add(clear); lists.add(cpane, gbcons); tabs = new JTabbedPane(); tabs.addTab("Send", icons.getIcon("new_message"), sendtab); // Initialize split panel add(lists); add(tabs); SGUI.adjustComponentSizes(FipaConversationPanel.this); SwingUtilities.invokeLater(new Runnable() { public void run() { // Hack!!! Doesn't work when called before panel is shown. setDividerLocation(0.35); } }); } //-------- methods -------- /** * Show error when no receiver is specified. */ protected void noReceiverSpecified() { String text = SUtil.wrapText("Cannot not send message, no receiver specified."); JOptionPane.showMessageDialog(SGUI.getWindowParent(FipaConversationPanel.this), text, "Message Error", JOptionPane.INFORMATION_MESSAGE); } /** * Get the message as a title. */ protected String getMessageTitle(Map msg) { StringBuffer title = new StringBuffer(); if(msg.get(SFipa.PERFORMATIVE)!=null) title.append(msg.get(SFipa.PERFORMATIVE)); else title.append("unknown"); title.append("("); if(msg.get(SFipa.CONTENT)!=null) title.append(msg.get(SFipa.CONTENT)); title.append(")"); String res = title.toString(); if(res.length()>25) res = res.substring(0, 21) + "...)"; return res; } /** * Add a received message. */ public void addMessage(final Map msg) { SwingUtilities.invokeLater(new Runnable() { public void run() { ((DefaultListModel)receivedmsgs.getModel()).addElement(msg); } }); } /** * Reset the message panel to an initial state. */ public void reset() { // Hack!!! Constructor chages thread context from swing to agent and back. // Make sure to call reset() afterwards. SwingUtilities.invokeLater(new Runnable() { public void run() { resetMessage(); setSentMessages(new Map[0]); setReceivedMessages(new Map[0]); while(tabs.getTabCount()>1) tabs.removeTabAt(tabs.getTabCount()-1); } }); } /** * Reset the message to send. */ public void resetMessage() { SwingUtilities.invokeLater(new Runnable() { public void run() { Map msg = new HashMap(); msg.put(SFipa.SENDER, agent.getComponentIdentifier()); sendpanel.setMessage(msg); } }); } //-------- helper methods -------- /** * Clone a message event. */ public Map cloneMessage(Map msg) { return new HashMap(msg); } /** * Get the message panel. */ public FipaMessagePanel getMessagePanel() { return sendpanel; } /** * Get the list of sent messages. */ public Map[] getSentMessages() { DefaultListModel model = (DefaultListModel)sentmsgs.getModel(); Map[] ret = new Map[model.getSize()]; model.copyInto(ret); return ret; } /** * Set the list of sent messages. */ public void setSentMessages(final Map[] msgs) { SwingUtilities.invokeLater(new Runnable() { public void run() { DefaultListModel model = (DefaultListModel)sentmsgs.getModel(); model.removeAllElements(); for(int i=0; i<msgs.length; i++) { model.addElement(msgs[i]); } } }); } /** * Set the list of received messages. */ public void setReceivedMessages(final Map[] msgs) { SwingUtilities.invokeLater(new Runnable() { public void run() { DefaultListModel model = (DefaultListModel)receivedmsgs.getModel(); model.removeAllElements(); for(int i=0; i<msgs.length; i++) { model.addElement(msgs[i]); } } }); } /** * Initialize the plugin from the properties. */ public void setProperties(Properties props) { try { // Load last state of message panel. String msg = props.getStringProperty(ConversationPlugin.LAST_MESSAGE); if(msg!=null) { final Map message = decodeMessage(msg); // Update sender. message.put(SFipa.SENDER, agent.getComponentIdentifier()); SwingUtilities.invokeLater(new Runnable() { public void run() { getMessagePanel().setMessage(message); } }); } else { resetMessage(); } // Load list of sent messages. final List sentmsgs = new ArrayList(); Property[] sents = props.getProperties(ConversationPlugin.SENT_MESSAGE); for(int i=0; i<sents.length; i++) { final boolean last = i == sents.length-1; Map message = decodeMessage(sents[i].getValue()); // Update sender. message.put(SFipa.SENDER, agent.getComponentIdentifier()); sentmsgs.add(0, message); // Re-revert order if(last) setSentMessages((Map[])sentmsgs.toArray(new Map[sentmsgs.size()])); } } catch(Exception e) { final String text = SUtil.wrapText("Could not decode stored message: "+e.getMessage()); SwingUtilities.invokeLater(new Runnable() { public void run() { JOptionPane.showMessageDialog(SGUI.getWindowParent(FipaConversationPanel.this), text, "Message problem", JOptionPane.INFORMATION_MESSAGE); } }); } } /** * Fill in message values from string. */ public Map decodeMessage(String msg) { Map map = (Map)JavaReader.objectFromXML(msg, null); // Todo: classloader!? return map; } /** * Get properties to be saved. */ public Properties getProperties() { if(!SwingUtilities.isEventDispatchThread()) throw new RuntimeException("Can only save properties from swing thread"); Properties props = new Properties(); // Save message displayed in message panel. Map message = getMessagePanel().getMessage(); String msg = (String)encodeMessage(message); props.addProperty(new Property(ConversationPlugin.LAST_MESSAGE, msg)); // Save list of sent messages (limit to 5 messages). Map[] msgs = getSentMessages(); Set saved = new HashSet(); // Used to avoid duplicates; for(int i=msgs.length-1; i>=0 && saved.size()<5; i--) // Backward loop to save newest messages. { msg = (String)encodeMessage(msgs[i]); if(!saved.contains(msg)) { props.addProperty(new Property(ConversationPlugin.SENT_MESSAGE, msg)); saved.add(msg); } } return props; } /** * Convert message to a string. * @param message The message. */ public String encodeMessage(Map message) { String msg = JavaWriter.objectToXML(message, null); // Todo: classloader!? return msg; } /** * Send a message. */ protected void sendMessage(final Map msg) { agent.scheduleStep(new IComponentStep() { public Object execute(IInternalAccess ia) { final IBDIInternalAccess scope = (IBDIInternalAccess)ia; final IMessageEvent me = createMessageEvent(scope, msg); scope.getEventbase().sendMessage(me); // Register message for conversations / replies. if(msg.get(SFipa.CONVERSATION_ID)!=null || msg.get(SFipa.REPLY_WITH)!=null) { regmsgs.add(me); scope.getEventbase().registerMessageEvent(me); me.addMessageEventListener(new IMessageEventListener() { public void messageEventReceived(AgentEvent ae) { scope.getEventbase().deregisterMessageEvent(me); me.removeMessageEventListener(this); } public void messageEventSent(AgentEvent ae) { } }); } SwingUtilities.invokeLater(new Runnable() { public void run() { // Register message for conversations / replies. if(msg.get(SFipa.CONVERSATION_ID)!=null || msg.get(SFipa.REPLY_WITH)!=null) { regmsgs.add(me); } ((DefaultListModel)sentmsgs.getModel()).addElement(cloneMessage(msg)); } }); return null; } }); } /** * Create a message event from a message map. */ protected IMessageEvent createMessageEvent(IBDIInternalAccess scope, Map msg) { IMessageEvent me = scope.getEventbase().createMessageEvent("fipamsg"); MessageType mt = new FIPAMessageType(); // (MessageType)msg.get(ConversationPlugin.ENCODED_MESSAGE_TYPE); for(int i=0; i<mt.getParameters().length; i++) { String name = mt.getParameters()[i].getName(); me.getParameter(name).setValue(msg.get(name)); } for(int i=0; i<mt.getParameterSets().length; i++) { String name = mt.getParameterSets()[i].getName(); if(msg.containsKey(name)) { Object[] values = (Object[])msg.get(name); for(int j=0; j<values.length; j++) { me.getParameterSet(name).addValue(values[j]); } } } return me; } /** * Create a map from a message event. * @param scope Used as marker to show that method requires running on component thread. * @param message The message event to convert to a map. */ public Map createMessageMap(IBDIInternalAccess scope, IMessageEvent message) { MessageType mt = message.getMessageType(); Map msg = new HashMap(); for(int i=0; i<mt.getParameters().length; i++) { String name = mt.getParameters()[i].getName(); msg.put(name, message.getParameter(name).getValue()); } for(int i=0; i<mt.getParameterSets().length; i++) { String name = mt.getParameterSets()[i].getName(); msg.put(name, message.getParameterSet(name).getValues()); } return msg; } /** * Display messages with performative and content instead of type. * This is because the type of messages is always "fipamsg" in the * conversation center. */ class MessageListCellRenderer extends DefaultListCellRenderer { public Component getListCellRendererComponent(JList list, Object value, int index, boolean sel, boolean hasfocus) { if(value instanceof Map) { Map msg = (Map)value; String perf = "n/a"; try { perf = (String)msg.get(SFipa.PERFORMATIVE); } catch(Exception e) { } String cont; try { cont =""+msg.get(SFipa.CONTENT); } catch(ContentException e) { cont = "invalid content"; } catch(Exception e) { cont = "n/a"; } value = perf + "( "+cont+ " )"; } return super.getListCellRendererComponent(list, value, index, sel, hasfocus); } } }