commit 7bbcfaed366ca44c98de751279232377fec6ddb4 Author: Jakub Wiewióra Date: Tue Jan 10 19:46:53 2023 +0100 Clean source diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..d2f0375 --- /dev/null +++ b/build.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jade/lib/commons-codec/commons-codec-1.3.jar b/jade/lib/commons-codec/commons-codec-1.3.jar new file mode 100644 index 0000000..957b675 Binary files /dev/null and b/jade/lib/commons-codec/commons-codec-1.3.jar differ diff --git a/jade/lib/jade.jar b/jade/lib/jade.jar new file mode 100644 index 0000000..0578475 Binary files /dev/null and b/jade/lib/jade.jar differ diff --git a/src/BookBuyerAgent.java b/src/BookBuyerAgent.java new file mode 100644 index 0000000..3847410 --- /dev/null +++ b/src/BookBuyerAgent.java @@ -0,0 +1,173 @@ +package jadelab2; + +import jade.core.Agent; +import jade.core.AID; +import jade.core.behaviours.*; +import jade.lang.acl.ACLMessage; +import jade.lang.acl.MessageTemplate; +import jade.domain.DFService; +import jade.domain.FIPAException; +import jade.domain.FIPAAgentManagement.DFAgentDescription; +import jade.domain.FIPAAgentManagement.ServiceDescription; + +public class BookBuyerAgent extends Agent { + private BookBuyerGui myGui; + private String targetBookTitle; + + //list of found sellers + private AID[] sellerAgents; + + protected void setup() { + targetBookTitle = ""; + System.out.println("Hello! " + getAID().getLocalName() + " is ready for the purchase order."); + myGui = new BookBuyerGui(this); + myGui.display(); + //time interval for buyer for sending subsequent CFP + //as a CLI argument + int interval = 20000; + Object[] args = getArguments(); + if (args != null && args.length > 0) interval = Integer.parseInt(args[0].toString()); + addBehaviour(new TickerBehaviour(this, interval) + { + protected void onTick() + { + //search only if the purchase task was ordered + if (!targetBookTitle.equals("")) + { + System.out.println(getAID().getLocalName() + ": I'm looking for " + targetBookTitle); + //update a list of known sellers (DF) + DFAgentDescription template = new DFAgentDescription(); + ServiceDescription sd = new ServiceDescription(); + sd.setType("book-selling"); + template.addServices(sd); + try + { + DFAgentDescription[] result = DFService.search(myAgent, template); + System.out.println(getAID().getLocalName() + ": the following sellers have been found"); + sellerAgents = new AID[result.length]; + for (int i = 0; i < result.length; ++i) + { + sellerAgents[i] = result[i].getName(); + System.out.println(sellerAgents[i].getLocalName()); + } + } + catch (FIPAException fe) + { + fe.printStackTrace(); + } + + myAgent.addBehaviour(new RequestPerformer()); + } + } + }); + } + + //invoked from GUI, when purchase was ordered + public void lookForTitle(final String title) + { + addBehaviour(new OneShotBehaviour() + { + public void action() + { + targetBookTitle = title; + System.out.println(getAID().getLocalName() + ": purchase order for " + targetBookTitle + " accepted"); + } + }); + } + + protected void takeDown() { + myGui.dispose(); + System.out.println("Buyer agent " + getAID().getLocalName() + " terminated."); + } + + private class RequestPerformer extends Behaviour { + private AID bestSeller; + private int bestPrice; + private int repliesCnt = 0; + private MessageTemplate mt; + private int step = 0; + + public void action() { + switch (step) { + case 0: + //call for proposal (CFP) to found sellers + ACLMessage cfp = new ACLMessage(ACLMessage.CFP); + for (int i = 0; i < sellerAgents.length; ++i) { + cfp.addReceiver(sellerAgents[i]); + } + cfp.setContent(targetBookTitle); + cfp.setConversationId("book-trade"); + cfp.setReplyWith("cfp"+System.currentTimeMillis()); //unique value + myAgent.send(cfp); + mt = MessageTemplate.and(MessageTemplate.MatchConversationId("book-trade"), + MessageTemplate.MatchInReplyTo(cfp.getReplyWith())); + step = 1; + break; + case 1: + //collect proposals + ACLMessage reply = myAgent.receive(mt); + if (reply != null) { + if (reply.getPerformative() == ACLMessage.PROPOSE) { + //proposal received + int price = Integer.parseInt(reply.getContent()); + if (bestSeller == null || price < bestPrice) { + //the best proposal as for now + bestPrice = price; + bestSeller = reply.getSender(); + } + } + repliesCnt++; + if (repliesCnt >= sellerAgents.length) { + //all proposals have been received + step = 2; + } + } + else { + block(); + } + break; + case 2: + //best proposal consumption - purchase + ACLMessage order = new ACLMessage(ACLMessage.ACCEPT_PROPOSAL); + order.addReceiver(bestSeller); + order.setContent(targetBookTitle); + order.setConversationId("book-trade"); + order.setReplyWith("order"+System.currentTimeMillis()); + myAgent.send(order); + mt = MessageTemplate.and(MessageTemplate.MatchConversationId("book-trade"), + MessageTemplate.MatchInReplyTo(order.getReplyWith())); + step = 3; + break; + case 3: + //seller confirms the transaction + reply = myAgent.receive(mt); + if (reply != null) { + if (reply.getPerformative() == ACLMessage.INFORM) { + //purchase succeeded + System.out.println(getAID().getLocalName() + ": " + targetBookTitle + " purchased for " + bestPrice + " from " + reply.getSender().getLocalName()); + System.out.println(getAID().getLocalName() + ": waiting for the next purchase order."); + targetBookTitle = ""; + //myAgent.doDelete(); + } + else { + System.out.println(getAID().getLocalName() + ": purchase has failed. " + targetBookTitle + " was sold in the meantime."); + } + step = 4; //this state ends the purchase process + } + else { + block(); + } + break; + } + } + + public boolean done() { + if (step == 2 && bestSeller == null) { + System.out.println(getAID().getLocalName() + ": " + targetBookTitle + " is not on sale."); + } + //process terminates here if purchase has failed (title not on sale) or book was successfully bought + return ((step == 2 && bestSeller == null) || step == 4); + } + } + +} diff --git a/src/BookBuyerGui.java b/src/BookBuyerGui.java new file mode 100644 index 0000000..50a2f6a --- /dev/null +++ b/src/BookBuyerGui.java @@ -0,0 +1,60 @@ +package jadelab2; + +import jade.core.AID; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +class BookBuyerGui extends JFrame { + private BookBuyerAgent myAgent; + + private JTextField titleField; + + BookBuyerGui(BookBuyerAgent a) { + super(a.getLocalName()); + + myAgent = a; + + JPanel p = new JPanel(); + p.setLayout(new GridLayout(2, 2)); + p.add(new JLabel("Title:")); + titleField = new JTextField(15); + p.add(titleField); + getContentPane().add(p, BorderLayout.CENTER); + + JButton addButton = new JButton("Search"); + addButton.addActionListener( new ActionListener() { + public void actionPerformed(ActionEvent ev) { + try { + String title = titleField.getText().trim(); + myAgent.lookForTitle(title); + titleField.setText(""); + } + catch (Exception e) { + JOptionPane.showMessageDialog(BookBuyerGui.this, "Invalid values. " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + } + } ); + p = new JPanel(); + p.add(addButton); + getContentPane().add(p, BorderLayout.SOUTH); + + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + myAgent.doDelete(); + } + } ); + + setResizable(false); + } + + public void display() { + pack(); + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int centerX = (int)screenSize.getWidth() / 2; + int centerY = (int)screenSize.getHeight() / 2; + setLocation(centerX - getWidth() / 2, centerY - getHeight() / 2); + setVisible(true); + } +} diff --git a/src/BookSellerAgent.java b/src/BookSellerAgent.java new file mode 100644 index 0000000..10da300 --- /dev/null +++ b/src/BookSellerAgent.java @@ -0,0 +1,118 @@ +package jadelab2; + +import jade.core.Agent; +import jade.core.behaviours.*; +import jade.lang.acl.ACLMessage; +import jade.lang.acl.MessageTemplate; +import jade.domain.DFService; +import jade.domain.FIPAException; +import jade.domain.FIPAAgentManagement.DFAgentDescription; +import jade.domain.FIPAAgentManagement.ServiceDescription; + +import java.util.*; + +public class BookSellerAgent extends Agent { + private Hashtable catalogue; + private BookSellerGui myGui; + + protected void setup() { + catalogue = new Hashtable(); + myGui = new BookSellerGui(this); + myGui.display(); + + //book selling service registration at DF + DFAgentDescription dfd = new DFAgentDescription(); + dfd.setName(getAID()); + ServiceDescription sd = new ServiceDescription(); + sd.setType("book-selling"); + sd.setName("JADE-book-trading"); + dfd.addServices(sd); + try { + DFService.register(this, dfd); + } + catch (FIPAException fe) { + fe.printStackTrace(); + } + + addBehaviour(new OfferRequestsServer()); + + addBehaviour(new PurchaseOrdersServer()); + } + + protected void takeDown() { + //book selling service deregistration at DF + try { + DFService.deregister(this); + } + catch (FIPAException fe) { + fe.printStackTrace(); + } + myGui.dispose(); + System.out.println("Seller agent " + getAID().getName() + " terminated."); + } + + //invoked from GUI, when a new book is added to the catalogue + public void updateCatalogue(final String title, final int price) { + addBehaviour(new OneShotBehaviour() { + public void action() { + catalogue.put(title, new Integer(price)); + System.out.println(getAID().getLocalName() + ": " + title + " put into the catalogue. Price = " + price); + } + } ); + } + + private class OfferRequestsServer extends CyclicBehaviour { + public void action() { + //proposals only template + MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.CFP); + ACLMessage msg = myAgent.receive(mt); + if (msg != null) { + String title = msg.getContent(); + ACLMessage reply = msg.createReply(); + Integer price = (Integer) catalogue.get(title); + if (price != null) { + //title found in the catalogue, respond with its price as a proposal + reply.setPerformative(ACLMessage.PROPOSE); + reply.setContent(String.valueOf(price.intValue())); + } + else { + //title not found in the catalogue + reply.setPerformative(ACLMessage.REFUSE); + reply.setContent("not-available"); + } + myAgent.send(reply); + } + else { + block(); + } + } + } + + + private class PurchaseOrdersServer extends CyclicBehaviour { + public void action() { + //purchase order as proposal acceptance only template + MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.ACCEPT_PROPOSAL); + ACLMessage msg = myAgent.receive(mt); + if (msg != null) { + String title = msg.getContent(); + ACLMessage reply = msg.createReply(); + Integer price = (Integer) catalogue.remove(title); + if (price != null) { + reply.setPerformative(ACLMessage.INFORM); + System.out.println(getAID().getLocalName() + ": " + title + " sold to " + msg.getSender().getLocalName()); + } + else { + //title not found in the catalogue, sold to another agent in the meantime (after proposal submission) + reply.setPerformative(ACLMessage.FAILURE); + reply.setContent("not-available"); + } + myAgent.send(reply); + } + else { + block(); + } + } + } + +} diff --git a/src/BookSellerGui.java b/src/BookSellerGui.java new file mode 100644 index 0000000..aed04d6 --- /dev/null +++ b/src/BookSellerGui.java @@ -0,0 +1,65 @@ +package jadelab2; + +import jade.core.AID; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +class BookSellerGui extends JFrame { + private BookSellerAgent myAgent; + + private JTextField titleField, priceField; + + BookSellerGui(BookSellerAgent a) { + super(a.getLocalName()); + + myAgent = a; + + JPanel p = new JPanel(); + p.setLayout(new GridLayout(2, 2)); + p.add(new JLabel("Title:")); + titleField = new JTextField(15); + p.add(titleField); + p.add(new JLabel("Price:")); + priceField = new JTextField(15); + p.add(priceField); + getContentPane().add(p, BorderLayout.CENTER); + + JButton addButton = new JButton("Add"); + addButton.addActionListener( new ActionListener() { + public void actionPerformed(ActionEvent ev) { + try { + String title = titleField.getText().trim(); + String price = priceField.getText().trim(); + myAgent.updateCatalogue(title, Integer.parseInt(price)); + titleField.setText(""); + priceField.setText(""); + } + catch (Exception e) { + JOptionPane.showMessageDialog(BookSellerGui.this, "Invalid values. " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + } + } ); + p = new JPanel(); + p.add(addButton); + getContentPane().add(p, BorderLayout.SOUTH); + + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + myAgent.doDelete(); + } + } ); + + setResizable(false); + } + + public void display() { + pack(); + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int centerX = (int)screenSize.getWidth() / 2; + int centerY = (int)screenSize.getHeight() / 2; + setLocation(centerX - getWidth() / 2, centerY - getHeight() / 2); + setVisible(true); + } +}