diff --git a/Makefile b/Makefile index 5d14f01..47a1a35 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,8 @@ QUORUM ?= 3 -MALFUNCTION ?= 0 DF_MAX_RESULT := $(shell expr $(QUORUM) + 5) PATH_PROJECT_JAR = target/election_structure-0.0.1-SNAPSHOT.jar PROJECT_GROUP = election_structure -JADE_AGENTS = election_structure:$(PROJECT_GROUP).App($(QUORUM), $(MALFUNCTION)); +JADE_AGENTS = election_structure:$(PROJECT_GROUP).App($(QUORUM)); JADE_FLAGS = -gui -jade_domain_df_maxresult $(DF_MAX_RESULT) -agents "$(JADE_AGENTS)" .PHONY: @@ -40,7 +39,5 @@ help: @echo " $$ make help" @echo " Shows this help resume" @echo "" - @echo "If wanted it's possible to change the quantity of agents by adding the variable QUORUM to the command, as seen in the next line" - @echo " $$ make build-and-run QUORUM=" - @echo "It's possible to activate a random agent malfunction to test timeout more effectively, as seen in the next line" - @echo " $$ make build-and-run MALFUNCTION=1" \ No newline at end of file + @echo "If wanted it's possible to change the quantity of agents by adding the variable QUORUM (with a value equal or greater than 1) to the command, as seen in the next line" + @echo " $$ make build-and-run QUORUM=" \ No newline at end of file diff --git a/src/main/java/election_structure/App.java b/src/main/java/election_structure/App.java index 2f2439c..e485e81 100644 --- a/src/main/java/election_structure/App.java +++ b/src/main/java/election_structure/App.java @@ -36,7 +36,6 @@ protected void setup() { int votersQuorum = 0; if (args != null && args.length > 0) { votersQuorum = Integer.parseInt(args[0].toString()); - randomAgentMalfunction = (Integer.parseInt(args[1].toString()) > 0); } int votingStarter = rand.nextInt(votersQuorum); diff --git a/src/main/java/election_structure/Ballot.java b/src/main/java/election_structure/Ballot.java index c99ea61..5feec07 100644 --- a/src/main/java/election_structure/Ballot.java +++ b/src/main/java/election_structure/Ballot.java @@ -2,12 +2,17 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumMap; +import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import jade.core.AID; import jade.core.behaviours.OneShotBehaviour; +import jade.core.behaviours.WakerBehaviour; import jade.domain.FIPAAgentManagement.DFAgentDescription; import jade.domain.FIPAAgentManagement.ServiceDescription; import jade.lang.acl.ACLMessage; @@ -15,11 +20,17 @@ public class Ballot extends BaseAgent { private Hashtable registeredVoters; - private ArrayList registeredCandidates; + private Hashtable registeredCandidates; - private Hashtable receivedVotes; + private Hashtable> receivedVotes; private Hashtable votingWeights; + private AtomicInteger receivedVotesCnt; + + private Boolean votesCollected = false; + + private final transient Object lock = new Object(); + @Override protected void setup() { @@ -28,7 +39,9 @@ protected void setup() { logger.log(Level.INFO, "I'm the ballot!"); this.registerDF(this, "Ballot", "ballot"); - + receivedVotes = new Hashtable<>(); + + receivedVotesCnt = new AtomicInteger(0); addBehaviour(handleMessages()); ArrayList foundAgent = new ArrayList<>( @@ -60,7 +73,31 @@ public void action () { } setupBallot(); - } else { + + } else if (msg.getContent().startsWith(Integer.toString(votingCode))) { + Types voterType = registeredVoters.get(msg.getSender()); + int vote = Integer.parseInt(splittedMsg[1]); + + if(!registeredCandidates.containsKey(vote)) vote = -1; + + synchronized(lock){ + Map updateMap = receivedVotes.get(vote); + + if (updateMap == null) updateMap = new EnumMap<>(Types.class); + + updateMap.put(voterType, updateMap.get(voterType) == null? 1 : updateMap.get(voterType) + 1); + receivedVotes.put(vote, updateMap); + + if(receivedVotesCnt.incrementAndGet() == 1){ + addBehaviour(timeoutBehaviour("collectVotes", TIMEOUT_LIMIT*3)); + } + + if(receivedVotesCnt.get() == registeredVoters.size()){ + votesCollected = true; + computeResults(); + } + } + } else { logger.log(Level.INFO, String.format("%s %s %s", getLocalName(), UNEXPECTED_MSG, msg.getSender().getLocalName())); } @@ -68,9 +105,26 @@ public void action () { }; } + @Override + protected WakerBehaviour timeoutBehaviour( String motivation, long timeout) { + return new WakerBehaviour(this, timeout) { + private static final long serialVersionUID = 1L; + + @Override + protected void onWake() { + if ( motivation.equals("collectVotes") && !votesCollected) { + votesCollected = true; + logger.log(Level.WARNING, + String.format("%s Agent voting time window ended! %s", ANSI_YELLOW, ANSI_RESET)); + computeResults(); + } + } + }; + } + private void setupBallot () { receivedVotes = new Hashtable<>(); - registeredCandidates = new ArrayList<>(); + registeredCandidates = new Hashtable<>(); registeredVoters = new Hashtable<>(); ArrayList foundVoters = new ArrayList<>( @@ -86,7 +140,7 @@ private void setupBallot () { if ( Arrays.toString(Types.values()).contains(el.getName()) ) { registeredVoters.put(voter.getName(), Types.valueOf(el.getName())); } else if ( el.getName().equals("Candidate") ) { - registeredCandidates.add(voter.getName()); + registeredCandidates.put( Integer.parseInt(el.getType()), voter.getName()); } } } @@ -95,13 +149,13 @@ private void setupBallot () { e.printStackTrace(); } - if ( registeredCandidates.size() < 1 || registeredVoters.size() < 2 ) { + if ( registeredCandidates.isEmpty() || registeredVoters.size() < 2 ) { logger.log(Level.WARNING, String.format("%s THERE CANNOT BE AN ELECTION WITH NO CANDIDATES OR NOT ENOUGH QUORUM %s", ANSI_YELLOW, ANSI_RESET)); ACLMessage msg = new ACLMessage(ACLMessage.INFORM); ArrayList foundMediators = findMediators( new String[]{ Integer.toString(votingCode) } ); - if ( foundMediators.size() > 0 ) { + if ( !foundMediators.isEmpty() ) { for ( DFAgentDescription fndMed : foundMediators ) { msg.addReceiver(fndMed.getName()); } @@ -123,7 +177,7 @@ private void startElection () { ACLMessage msg = new ACLMessage(ACLMessage.INFORM); ArrayList foundMediators = findMediators( new String[]{ Integer.toString(votingCode) } ); - if ( foundMediators.size() > 0 ) { + if ( !foundMediators.isEmpty() ) { for ( DFAgentDescription fndMed : foundMediators ) { msg.addReceiver(fndMed.getName()); } @@ -136,4 +190,59 @@ private void startElection () { e.printStackTrace(); } } + + private void computeResults() { + HashMap results = new HashMap<>(); + + int sum; + + int maxVote = -1; + + ArrayList winnerCandidates = new ArrayList<>(); + + for(Map.Entry> entry : receivedVotes.entrySet()){ + sum = 0; + Map votesCount = entry.getValue(); + + for(Map.Entry entry2 : votesCount.entrySet()){ + sum += votingWeights.get(entry2.getKey()) * entry2.getValue(); + } + + results.put(entry.getKey(), sum); + + if(sum == maxVote){ + winnerCandidates.add(entry.getKey()); + } + + if(sum > maxVote){ + maxVote = sum; + winnerCandidates.clear(); + winnerCandidates.add(entry.getKey()); + } + + } + + ACLMessage msg = new ACLMessage(ACLMessage.INFORM); + + msg.addReceiver(findMediators(new String[]{ Integer.toString(votingCode) }).get(0).getName()); + + StringBuilder strBld = new StringBuilder(); + for ( Integer winner : winnerCandidates ) { + strBld.append(String.format("%d ", winner)); + } + + String winners = strBld.toString().trim(); + msg.setContent(String.format("%s WinnersCount %d VotesCount %d Winners %s", "RESULTS", winnerCandidates.size(), maxVote, winners)); + send(msg); + + strBld.setLength(0); + for ( Map.Entry entry : results.entrySet() ) { + strBld.append(String.format("%d %d ", entry.getKey(), entry.getValue())); + } + + String voteLog = strBld.toString().trim(); + msg.setContent(String.format("%s Size %d %s", "ELECTIONLOG", results.size(),voteLog)); + send(msg); + + } } diff --git a/src/main/java/election_structure/BaseAgent.java b/src/main/java/election_structure/BaseAgent.java index 568ba1b..0fee5df 100644 --- a/src/main/java/election_structure/BaseAgent.java +++ b/src/main/java/election_structure/BaseAgent.java @@ -62,7 +62,6 @@ public abstract class BaseAgent extends Agent { protected static final Logger logger = Logger.getLogger(BaseAgent.class.getName()); protected static final Long TIMEOUT_LIMIT = 1000L; - protected static boolean randomAgentMalfunction = false; protected boolean brokenAgent = false; protected boolean candidate = false; diff --git a/src/main/java/election_structure/Mediator.java b/src/main/java/election_structure/Mediator.java index 647d954..b3d2492 100644 --- a/src/main/java/election_structure/Mediator.java +++ b/src/main/java/election_structure/Mediator.java @@ -4,6 +4,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; import java.util.Stack; import java.util.logging.Level; @@ -11,7 +13,10 @@ import jade.core.Agent; import jade.core.behaviours.OneShotBehaviour; import jade.core.behaviours.WakerBehaviour; +import jade.domain.DFService; +import jade.domain.FIPAException; import jade.domain.FIPAAgentManagement.DFAgentDescription; +import jade.domain.FIPAAgentManagement.ServiceDescription; import jade.lang.acl.ACLMessage; public class Mediator extends BaseAgent { @@ -23,10 +28,8 @@ public class Mediator extends BaseAgent { private int totalQuorum = 0; private Boolean ballotCreated = false; private Boolean ballotRequested = false; - - private Hashtable votingLog; + private Hashtable candidatures; - private ArrayList winners; private ArrayList preCandidates; private Stack candidateCodes; @@ -52,9 +55,6 @@ public void action () { if (msg.getContent().startsWith(START)) { votingCode = votingCodeGenerator(); - - votingLog = new Hashtable<>(); - winners = new ArrayList<>(); setupVotingWeights(); genCandidateCodes(); @@ -74,9 +74,6 @@ public void action () { candidatures = new Hashtable<>(); preCandidates = new ArrayList<>(); - - - } else if ( msg.getContent().startsWith(INFORM) ) { if (splittedMsg[1].equals(QUORUM)) { addBehaviour(timeoutBehaviour( "registration", TIMEOUT_LIMIT)); @@ -101,14 +98,14 @@ public void action () { ACLMessage msg2 = msg.createReply(); StringBuilder strBld = new StringBuilder(); - for ( Types type : votingWeights.keySet() ) { - strBld.append(String.format("%s %d ", type.toString(), votingWeights.get(type))); + for ( Map.Entry entry : votingWeights.entrySet() ) { + strBld.append(String.format("%s %d ", entry.getKey().toString(), entry.getValue())); } msg2.setContent(String.format("VOTEID %d WEIGHTS %d %s", votingCode, votingWeights.size(), strBld.toString().trim())); send(msg2); } else if ( msg.getContent().startsWith("FAILURE") ) { - deleteElection(Integer.parseInt(splittedMsg[1])); + resetElection(myAgent); } else if ( msg.getContent().startsWith("READY") ) { try { int votCode = Integer.parseInt(splittedMsg[1]); @@ -123,6 +120,29 @@ public void action () { e.printStackTrace(); } + } else if(msg.getContent().startsWith("RESULTS") ) { + + informWinner(msg.getContent()); + + String winnersCnt = splittedMsg[2]; + String winnersVote = splittedMsg[4]; + + String winnersCode = ""; + + for(int i = 6; i < splittedMsg.length; i++){ + winnersCode += splittedMsg[i] + " "; + } + + String results = String.format(" ELECTION RESULTS FOR VOTING %d: \n", votingCode); + results = results.concat(String.format(" \t\tWinner count: %s\n",winnersCnt)); + results = results.concat(String.format(" \t\tWinner received votes %s\n", winnersVote)); + results = results.concat(String.format(" \t\tWinner Codes: %s ", winnersCode)); + + logger.log(Level.INFO, String.format("%s%s%s", ANSI_PURPLE, results, ANSI_RESET)); + + } else if(msg.getContent().startsWith("ELECTIONLOG") ) { + logger.log(Level.INFO, String.format("%s %s %s", ANSI_PURPLE, msg.getContent(), ANSI_RESET)); + resetElection(myAgent); } else { logger.log(Level.INFO, String.format("%s RECEIVED AN UNEXPECTED MESSAGE FROM %s", getLocalName(), msg.getSender().getLocalName())); @@ -183,48 +203,87 @@ protected WakerBehaviour timeoutBehaviour( String motivation, long timeout) { @Override protected void onWake() { - if ( motivation.equals("registration") ) { - if(!ballotRequested){ + if ( votingCode != -1 && motivation.equals("registration") && !ballotCreated ) { logger.log(Level.WARNING, String.format("%s Agent registration timed out! %s", ANSI_YELLOW, ANSI_RESET)); createBallot(); - } - } else if (motivation.equals("Create-Ballot")){ - if(!ballotCreated){ + } else if ( votingCode != -1 && motivation.equals("Create-Ballot") && !ballotCreated ){ logger.log(Level.WARNING, String.format("%s Ballot creation timed out! %s", ANSI_YELLOW, ANSI_RESET)); createBallot(); - } } } }; } - private void informWinner(){ - - } + private void informWinner(String content){ + ACLMessage msg2 = new ACLMessage(ACLMessage.INFORM); + + ArrayList foundVotingParticipants; + String [] types = { Integer.toString(votingCode), "voter" }; + + foundVotingParticipants = new ArrayList<>( + Arrays.asList(searchAgentByType(types))); - protected void resetVoting(Agent myAgent){ + foundVotingParticipants.forEach(vot -> + msg2.addReceiver(vot.getName()) + ); + msg2.setContent(content); + send(msg2); + + logger.log(Level.INFO, + String.format("%s %s SENT ELECTION RESULTS TO ALL VOTERS! %s", ANSI_PURPLE , getLocalName(), ANSI_RESET)); } - private void computeResults() { + protected void resetElection(Agent myAgent){ + registeredQuorum = 0; + totalQuorum = 0; + ballotCreated = false; + ballotRequested = false; + + candidatures = new Hashtable<>(); + preCandidates = new ArrayList<>(); + candidateCodes = new Stack<>(); + + deleteAgentServices(myAgent, Integer.toString(votingCode), Integer.toString(votingCode)); + deleteAgentServices(myAgent, "voter", "Candidate"); + votingCode = -1; + + logger.log(Level.WARNING, ANSI_YELLOW + "VOTING ENDED!" + ANSI_RESET); } - private void deleteElection (int receivedVotingCode) { - logger.log(Level.INFO, String.format("%s DELETING ELECTION WITH CODE %d %s", ANSI_CYAN, receivedVotingCode, ANSI_RESET)); + private void deleteAgentServices(Agent myAgent, String searchAttr, String deleteCondition) { + DFAgentDescription[] dfd = searchAgentByType(searchAttr); + + for (int i = 0; i < dfd.length; i++) { + Iterator it = dfd[i].getAllServices(); - /* - * HERE, WE SHOULD IMPLEMENT VOTING DELETION LOGIC - */ + ServiceDescription sd; + while (it.hasNext()) { + sd = it.next(); + if( sd.getType().equals(deleteCondition) || sd.getName().equals(deleteCondition) ){ + dfd[i].removeServices(sd); + break; + } + } + + try { + DFService.modify(myAgent, dfd[i]); + } catch (FIPAException e) { + logger.log(Level.SEVERE, ANSI_RED + "ERROR WHILE MODIFYING AGENTS" + ANSI_RESET); + e.printStackTrace(); + } + } } + private void setupVotingWeights () { votingWeights = new Hashtable<>(); for ( Types element : Types.values() ) { - votingWeights.put(element, rand.nextInt(5)); + votingWeights.put(element, rand.nextInt(1,6)); } } @@ -287,12 +346,12 @@ private void genCandidateCodes() { private void informCandidatesProposals ( ArrayList foundVotingParticipants ) { ACLMessage msg = new ACLMessage(ACLMessage.INFORM); - foundVotingParticipants.forEach(vot -> { - msg.addReceiver(vot.getName()); - }); + foundVotingParticipants.forEach(vot -> + msg.addReceiver(vot.getName()) + ); - for ( AID candAID : candidatures.keySet() ) { - Candidature cdtr = candidatures.get(candAID); + for ( Map.Entry entry : candidatures.entrySet() ) { + Candidature cdtr = entry.getValue(); String content = String.format("CANDIDATE %d %s %s", cdtr.candidatureNumber, PROPOSAL, cdtr.proposal); msg.setContent(content); diff --git a/src/main/java/election_structure/Voter.java b/src/main/java/election_structure/Voter.java index 421e16e..45cfd49 100644 --- a/src/main/java/election_structure/Voter.java +++ b/src/main/java/election_structure/Voter.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Hashtable; import java.util.List; +import java.util.Map; import java.util.logging.Level; import jade.core.AID; @@ -22,7 +23,8 @@ public class Voter extends BaseAgent { private Hashtable recvProposals; private int candidatesCount; private int candidatesExpected; - int selectionMethod; + private int selectionMethod; + private int myCandidatureCode = -1;; @Override protected void setup() { @@ -38,14 +40,6 @@ protected void setup() { this.registerDF(this, myVotingType.toString(), myVotingType.toString()); - if ( !randomAgentMalfunction || rand.nextInt(11) != 10 ) { - logger.log(Level.INFO, String.format("I'm the %s!", getLocalName())); - } else { - brokenAgent = true; - logger.log(Level.WARNING, - String.format("%s I'm agent %s and I have a malfunction! %s", ANSI_CYAN, getLocalName(), ANSI_RESET)); - } - if ( rand.nextInt(11) <= 5 ) { logger.log(Level.INFO, String.format("I'm the %s!", getLocalName())); } else { @@ -73,7 +67,7 @@ public void action () { try { AID foundMediator = null; - if ( foundAgents.size() > 0 ) { + if ( !foundAgents.isEmpty() ) { foundMediator = foundAgents.get(0).getName(); msg2.addReceiver(foundMediator); @@ -92,6 +86,7 @@ public void action () { votingCode = Integer.parseInt(splittedMsg[1]); recvProposals = new Hashtable<>(); candidatesCount = 0; + myCandidatureCode = -1; registerDF(myAgent, Integer.toString(votingCode), Integer.toString(votingCode)); @@ -135,12 +130,22 @@ public void action () { requestCandidateCode(); } else if ( msg.getContent().startsWith("CANDIDCODE") ) { - registerCandidature(myAgent, Integer.parseInt(splittedMsg[1]), msg); + myCandidatureCode = Integer.parseInt(splittedMsg[1]); + registerCandidature(myAgent, myCandidatureCode, msg); } else if ( msg.getContent().startsWith("CANDIDATE") ) { String prop = msg.getContent().substring(msg.getContent().indexOf(PROPOSAL) + PROPOSAL.length() + 1); recvProposals.put(splittedMsg[1], prop); candidatesCount++; + } else if ( msg.getContent().startsWith("RESULTS") ) { + String logContent = String.format("%s RECEIVED THE RESULTS OF ELECTION %d!", getLocalName(), votingCode); + + for( int i = 6; i < splittedMsg.length; i++ ){ + if ( candidate && Integer.parseInt(splittedMsg[i]) == myCandidatureCode ) + logContent = String.format("%s I'M %s AND I WON THE ELECTION WITH CODE %d! %s", ANSI_GREEN, getLocalName(), votingCode, ANSI_RESET); + } + + logger.log(Level.INFO, logContent); } else { logger.log(Level.INFO, String.format("%s %s %s", getLocalName(), UNEXPECTED_MSG, msg.getSender().getLocalName())); @@ -196,7 +201,7 @@ private void informVotingRegistration() { String sendMsg = String.format("%s IN %d", REGISTERED, votingCode); - if ( candidate == true ) + if ( candidate ) sendMsg += " AND CANDIDATE"; informMsg.setContent(sendMsg); @@ -263,20 +268,21 @@ private void vote(){ private String chooseVote(){ - String selectedCandidate = new String(); + String selectedCandidate = ""; - int max =-1, min = Integer.MAX_VALUE; + int max =-1; + int min = Integer.MAX_VALUE; int len; - for(String candID : recvProposals.keySet()){ - len = recvProposals.get(candID).length(); + for(Map.Entry entry : recvProposals.entrySet()){ + len = entry.getValue().length(); if(len > max && selectionMethod == 1){ max = len; - selectedCandidate = candID; + selectedCandidate = entry.getKey(); } if(len < min && selectionMethod == 0){ min = len; - selectedCandidate = candID; + selectedCandidate = entry.getKey(); } }