package qora.voting; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import com.google.common.primitives.Bytes; import com.google.common.primitives.Ints; import database.DBSet; import qora.account.Account; import qora.crypto.Base58; import utils.Pair; public class Poll { private static final int CREATOR_LENGTH = 25; private static final int NAME_SIZE_LENGTH = 4; private static final int DESCRIPTION_SIZE_LENGTH = 4; private static final int OPTIONS_SIZE_LENGTH = 4; private Account creator; private String name; private String description; private List<PollOption> options; public Poll(Account creator, String name, String description, List<PollOption> options) { this.creator = creator; this.name = name; this.description = description; this.options = options; } //GETTERS/SETTERS public Account getCreator() { return this.creator; } public String getName() { return this.name; } public String getDescription() { return this.description; } public List<PollOption> getOptions() { return this.options; } public boolean isConfirmed() { return DBSet.getInstance().getPollMap().contains(this); } public boolean hasVotes() { for(PollOption option: this.options) { if(option.getVoters().size() > 0) { return true; } } return false; } public BigDecimal getTotalVotes() { BigDecimal votes = BigDecimal.ZERO.setScale(8); for(PollOption option: this.options) { votes = votes.add(option.getVotes()); } return votes; } public List<Pair<Account, PollOption>> getVotes() { List<Pair<Account, PollOption>> votes = new ArrayList<Pair<Account, PollOption>>(); for(PollOption option: this.options) { for(Account voter: option.getVoters()) { Pair<Account, PollOption> vote = new Pair<Account, PollOption>(voter, option); votes.add(vote); } } return votes; } public List<Pair<Account, PollOption>> getVotes(List<Account> accounts) { List<Pair<Account, PollOption>> votes = new ArrayList<Pair<Account, PollOption>>(); for(PollOption option: this.options) { for(Account voter: option.getVoters()) { if(accounts.contains(voter)) { Pair<Account, PollOption> vote = new Pair<Account, PollOption>(voter, option); votes.add(vote); } } } return votes; } public PollOption getOption(String option) { for(PollOption pollOption: this.options) { if(pollOption.getName().equals(option)) { return pollOption; } } return null; } //PARSE public static Poll parse(byte[] data) throws Exception { int position = 0; //READ CREATOR byte[] creatorBytes = Arrays.copyOfRange(data, position, position + CREATOR_LENGTH); Account creator = new Account(Base58.encode(creatorBytes)); position += CREATOR_LENGTH; //READ NAME SIZE byte[] nameLengthBytes = Arrays.copyOfRange(data, position, position + NAME_SIZE_LENGTH); int nameLength = Ints.fromByteArray(nameLengthBytes); position += NAME_SIZE_LENGTH; if(nameLength < 1 || nameLength > 400) { throw new Exception("Invalid name length"); } //READ NAME byte[] nameBytes = Arrays.copyOfRange(data, position, position + nameLength); String name = new String(nameBytes, StandardCharsets.UTF_8); position += nameLength; //READ DESCRIPTION byte[] descriptionLengthBytes = Arrays.copyOfRange(data, position, position + DESCRIPTION_SIZE_LENGTH); int descriptionLength = Ints.fromByteArray(descriptionLengthBytes); position += DESCRIPTION_SIZE_LENGTH; if(descriptionLength < 1 || descriptionLength > 4000) { throw new Exception("Invalid description length"); } byte[] descriptionBytes = Arrays.copyOfRange(data, position, position + descriptionLength); String description = new String(descriptionBytes, StandardCharsets.UTF_8); position += descriptionLength; //READ OPTIONS SIZE byte[] optionsLengthBytes = Arrays.copyOfRange(data, position, position + OPTIONS_SIZE_LENGTH); int optionsLength = Ints.fromByteArray(optionsLengthBytes); position += OPTIONS_SIZE_LENGTH; if(optionsLength < 1 || optionsLength > 100) { throw new Exception("Invalid options length"); } //READ OPTIONS List<PollOption> options = new ArrayList<PollOption>(); for(int i=0; i<optionsLength; i++) { PollOption option = PollOption.parse(Arrays.copyOfRange(data, position, data.length)); position += option.getDataLength(); options.add(option); } return new Poll(creator, name, description, options); } @SuppressWarnings("unchecked") public JSONObject toJson() { //GET BASE JSONObject poll = new JSONObject(); //ADD NAME/DESCRIPTIONS/OPTIONS poll.put("creator", this.getCreator().getAddress()); poll.put("name", this.getName()); poll.put("description", this.getDescription()); JSONArray jsonOptions = new JSONArray(); for(PollOption option: this.options) { jsonOptions.add(option.toJson()); } poll.put("options", jsonOptions); return poll; } public byte[] toBytes() { byte[] data = new byte[0]; //WRITE CREATOR try { data = Bytes.concat(data , Base58.decode(this.creator.getAddress())); } catch(Exception e) { //DECODE EXCEPTION } //WRITE NAME SIZE byte[] nameBytes = this.name.getBytes(StandardCharsets.UTF_8); int nameLength = nameBytes.length; byte[] nameLengthBytes = Ints.toByteArray(nameLength); data = Bytes.concat(data, nameLengthBytes); //WRITE NAME data = Bytes.concat(data, nameBytes); //WRITE DESCRIPTION SIZE byte[] valueBytes = this.description.getBytes(StandardCharsets.UTF_8); int valueLength = valueBytes.length; byte[] valueLengthBytes = Ints.toByteArray(valueLength); data = Bytes.concat(data, valueLengthBytes); //WRITE DESCRIPTION data = Bytes.concat(data, valueBytes); //WRITE OPTIONS SIZE byte[] optionsLengthBytes = Ints.toByteArray(this.options.size()); data = Bytes.concat(data, optionsLengthBytes); //WRITE OPTIONS for(PollOption option: this.options) { data = Bytes.concat(data, option.toBytes()); } return data; } public int getDataLength() { int length = CREATOR_LENGTH + NAME_SIZE_LENGTH + this.name.getBytes(StandardCharsets.UTF_8).length + DESCRIPTION_SIZE_LENGTH + this.description.getBytes(StandardCharsets.UTF_8).length + OPTIONS_SIZE_LENGTH; for(PollOption option: this.options) { length += option.getDataLength(); } return length; } public int addVoter(Account voter, int optionIndex) { //CHECK IF WE HAD A PREVIOUS VOTE IN THIS POLL int previousOption = -1; for(PollOption option: this.options) { if(option.hasVoter(voter)) { previousOption = this.options.indexOf(option); } } if(previousOption != -1) { //REMOVE VOTE this.options.get(previousOption).removeVoter(voter); } //ADD NEW VOTE this.options.get(optionIndex).addVoter(voter); return previousOption; } public void deleteVoter(Account voter, int optionIndex) { this.options.get(optionIndex).removeVoter(voter); } //COPY public Poll copy() { try { byte[] bytes = this.toBytes(); return parse(bytes); } catch(Exception e) { return null; } } }