diff --git a/voting-booth/src/main/java/meerkat/voting/ui/GraphicalUserInterface.java b/voting-booth/src/main/java/meerkat/voting/ui/GraphicalUserInterface.java new file mode 100644 index 0000000..4611899 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/GraphicalUserInterface.java @@ -0,0 +1,250 @@ +package meerkat.voting.ui; + +import com.google.common.util.concurrent.FutureCallback; +import meerkat.protobuf.Voting; +import meerkat.voting.controller.callbacks.*; +import meerkat.voting.ui.uicommands.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.*; + +import static java.lang.System.in; + +/** + * Created by Vladimir Eliezer Tokarev on 9/17/2016. + * This class forwards and receives different information for voter voting process + * and displays it in to voting booth gui + */ +public class GraphicalUserInterface implements VotingBoothUI, Runnable { + private static final Logger logger = LoggerFactory.getLogger(SystemConsoleUI.class); + + private CommandPend cmdPend; + private Date startWaitingTime; + + private volatile boolean shutDownHasBeenCalled; + + + public GraphicalUserInterface() { + final int tickDurationInMilliSeconds = 10; // period between view update calls + + logger.info("A Voting Booth graphical user interface is constructed"); + cmdPend = new CommandPend<>(); + + startWaitingTime = null; + Timer timer = new Timer(); + timer.scheduleAtFixedRate(new TickerTimerTask(cmdPend), new Date(), tickDurationInMilliSeconds); + + shutDownHasBeenCalled = false; + } + + /** + * the run() method. Simply loops and takes the UI's pending command and handles it accordingly + */ + @Override + public void run() { + logger.info("Graphical user interface starts running"); + while (! shutDownHasBeenCalled) { + try { + UICommand command = cmdPend.take(); + handleSingleCommand(command); + } + catch (InterruptedException e) { + logger.warn ("Interrupted while reading the pending command " + e); + } + } + } + + /** + * chooses the next method to run according to the type of the given UICommand. + * Special case for the TickCommand. + * As this command is registered in the queue constantly, we simply ignore this command if the UI is not in + * a waiting state + * @param command any valid UICommand + */ + private void handleSingleCommand(UICommand command) { + if (!(command instanceof TickCommand)) { + if (startWaitingTime != null) { + stopWaiting(); + } + } + + if (command instanceof StartSessionUICommand) { + doShowWelcomeScreen((StartSessionUICommand)command); + } + else if (command instanceof ChannelChoiceUICommand) { + doAskChannelChoiceQuestions((ChannelChoiceUICommand)command); + } + else if (command instanceof RaceVotingUICommand) { + doAskVotingQuestions((RaceVotingUICommand)command); + } + else if (command instanceof CastOrAuditUICommand) { + doCastOrAudit ((CastOrAuditUICommand)command); + } + else if (command instanceof FatalErrorUICommand) { + doFatalError((FatalErrorUICommand)command); + } + else if (command instanceof WaitForFinishUICommand) { + doWaitForFinish((WaitForFinishUICommand)command); + } + else if (command instanceof TickCommand) { + doTick (); + } + else { + String errorMessage = "handleSingleCommand: unknown type of UICommand received: " + + command.getClass().getName(); + logger.error(errorMessage); + throw new RuntimeException(errorMessage); + } + } + + /** + * doTick do a simple action + */ + private void doTick() { + } + + + @Override + public void callShutDown() { + + } + + /** + * start a new session by registering a StartSessionUICommand + * @param callback - a boolean future callback to return when done + */ + @Override + public void startNewVoterSession(FutureCallback callback) { + logger.debug("Graphical user interface call to startNewVoterSession"); + } + + /** + * welcomes the new voter at the beginning of the session + * @param command a StartSessionUICommand with a acallback + */ + private void doShowWelcomeScreen(StartSessionUICommand command) { + logger.debug("Graphical user interface entered doShowWelcomeScreen"); + } + + /** + * marks that the waiting, for something else to have happened, is finished + */ + private void stopWaiting () { + } + + /** + * call for the channel choice phase by registering a ChannelChoiceUICommand in the queue + * @param questions questions to determine the right voting channel for this voter + * @param callback that's where we store the answers to decide channel upon for the current voter + */ + @Override + public void chooseChannel(List questions, FutureCallback> callback) { + logger.debug("Graphical user interface call to chooseChannel"); + } + + /** + * lists the channel choice questions to the voter and gathers the voter's answers + * @param command a ChannelChoiceUICommand with the data and a callback + */ + private void doAskChannelChoiceQuestions (ChannelChoiceUICommand command) { + logger.debug("Graphical user interface doAskChannelChoiceQuestions"); + } + + /** + * call for the race voting question phase by registering a RaceVotingUICommand in the queue + * @param questions all ballot questions to present to the voter + * @param callback the responses to the questions collected by the UI, to send back to the controller. Responses are null if voter chose to cancel session + */ + @Override + public void askVoterQuestions(List questions, FutureCallback> callback) { + logger.debug("Graphical user interface call to chooseChannel"); + } + + /** + * lists the race voting questions to the voter and gathers the voter's answers + * @param command a RaceVotingUICommand with a callback + */ + private void doAskVotingQuestions (RaceVotingUICommand command) { + logger.debug("Graphical user interface doAskVotingQuestions"); + } + + /** + * call for the cast-or-audit phase by registering a CastOrAuditUICommand in the queue + * @param callback the returned choice of how to finalize the ballot + */ + @Override + public void castOrAudit(FutureCallback callback) { + logger.debug("Graphical user interface call to castOrAudit"); + } + + /** + * asks the voter whether to cast or audit the ballot + * @param command a simple CastOrAuditUICommand with the callback + */ + private void doCastOrAudit(CastOrAuditUICommand command) { + logger.debug("Graphical user interface entered doCastOrAudit"); + } + + /** + * makes the UI (and voter) wait for something else to happen, by registering a WaitForFinishUICommand in the queue + * @param message a message to show the user on the UI device while waiting + * @param callback a success return value of the wait (cancelling returns false) + */ + @Override + public void notifyVoterToWaitForFinish(Voting.UIElement message, FutureCallback callback) { + logger.debug("Graphical user interface call to notifyVoterToWaitForFinish"); + } + + /** + * Tells the voter (in the console) to wait until some other process is finished + * @param command a simple WaitForFinishUICommand with the callback + */ + public void doWaitForFinish (WaitForFinishUICommand command) { + logger.debug("Graphical user interface entered doWaitForFinish"); + } + + /** + * show an error to the voter. Halts the system until a technician handles it + * @param errorMessage message to show in UI device + * @param callback returns interrupt + */ + @Override + public void showErrorMessageAndHalt(Voting.UIElement errorMessage, FutureCallback callback) { + logger.debug("Graphical user interface call to showErrorMessageAndHalt"); + } + + /** + * show an error to the voter. let him press a (chosen) button for handling the error. + * @param errorMessage message to show in UI device + * @param buttonLabels labels for buttons to present to voter + * @param callback the number of the selected button + */ + @Override + public void showErrorMessageWithButtons(Voting.UIElement errorMessage, Voting.UIElement[] buttonLabels, FutureCallback callback) { + logger.debug("Graphical user interface call to showErrorMessageWithButtons"); + } + + /** + * show an error to the voter. let him press a (chosen) button for handling the error. + * @param command a FatalErrorUICommand with the callback + */ + private void doFatalError (FatalErrorUICommand command) { + logger.debug("Graphical user interface entered doFatalError"); + } + + /** + * present the questions to the voter console sequentially. + * Voter may choose at any time to skip a question, go back or even cancel the whole session + * @param questions list of questions to present + * @return list of answers to the questions (at the same order) + * @throws VoterCancelThrowable this is thrown if a voter chose to cancel in the middle of the process + * @throws IOException + */ + private List askVoterForAnswers(List questions) throws VoterCancelThrowable, IOException { + return new ArrayList(); + } +} diff --git a/voting-booth/src/main/java/meerkat/voting/ui/SystemConsoleUI.java b/voting-booth/src/main/java/meerkat/voting/ui/SystemConsoleUI.java index 3b34da8..8c3f8aa 100644 --- a/voting-booth/src/main/java/meerkat/voting/ui/SystemConsoleUI.java +++ b/voting-booth/src/main/java/meerkat/voting/ui/SystemConsoleUI.java @@ -304,7 +304,7 @@ public class SystemConsoleUI implements VotingBoothUI, Runnable { if (message.getType() != UIElementDataType.TEXT) { messageString = "Default message: encountered an error. System halting"; } else { - messageString = bytesToString(message.getData()); + messageString = UIUtils.bytesToString(message.getData()); } System.out.println(messageString); System.out.print ("Waiting : ."); @@ -345,7 +345,7 @@ public class SystemConsoleUI implements VotingBoothUI, Runnable { if (errorMessage.getType() != UIElementDataType.TEXT) { errorMessageString = "Default message: encountered an error. System halting"; } else { - errorMessageString = bytesToString(errorMessage.getData()); + errorMessageString = UIUtils.bytesToString(errorMessage.getData()); } UIElement[] buttonLabels = command.getButtonLabels(); @@ -354,7 +354,7 @@ public class SystemConsoleUI implements VotingBoothUI, Runnable { if (buttonLabels[i].getType() != UIElementDataType.TEXT) { buttonLabelStrings[i] = ""; } else { - buttonLabelStrings[i] = bytesToString(errorMessage.getData()); + buttonLabelStrings[i] = UIUtils.bytesToString(errorMessage.getData()); } } @@ -406,29 +406,6 @@ public class SystemConsoleUI implements VotingBoothUI, Runnable { return s; } - - /** - * asserts that the question data matches the types that we can handle in the ConsoleUI - * (better and more sophisticated UI will be able to handle more types of data) - * @param questions list of the questions - */ - private void assertQuestionsAreValid (List questions) { - for (int index = 0; index < questions.size(); ++index) { - BallotQuestion question = questions.get(index); - if (question.getIsMandatory()) { - String errorMessage = "askVoterQuestions: question number " + index + " is marked as mandatory"; - logger.error(errorMessage); - throw new UnsupportedOperationException(errorMessage); - } - if (!isQuestionOnlyText(question)) { - String errorMessage = "askVoterQuestions: question number " + index + " is not only text"; - logger.error(errorMessage); - throw new UnsupportedOperationException(errorMessage); - } - } - } - - /** * present the questions to the voter console sequentially. * Voter may choose at any time to skip a question, go back or even cancel the whole session @@ -439,7 +416,7 @@ public class SystemConsoleUI implements VotingBoothUI, Runnable { */ private List askVoterForAnswers(List questions) throws VoterCancelThrowable, IOException { - assertQuestionsAreValid (questions); + UIUtils.assertQuestionsAreValid (questions, logger); List answers = new ArrayList<>(); int index = 0; @@ -459,11 +436,11 @@ public class SystemConsoleUI implements VotingBoothUI, Runnable { answers.remove(index); } else if (s.equals("skip") || s.equals("s")) { - answers.add(translateStringAnswerToProtoBufMessageAnswer("")); + answers.add(UIUtils.translateStringAnswerToProtoBufMessageAnswer("")); ++index; } else { - answers.add(translateStringAnswerToProtoBufMessageAnswer(s)); + answers.add(UIUtils.translateStringAnswerToProtoBufMessageAnswer(s)); ++index; } } @@ -475,57 +452,20 @@ public class SystemConsoleUI implements VotingBoothUI, Runnable { * @param question a text ballot question */ private void showQuestionInConsole(BallotQuestion question) { - if (!isQuestionOnlyText(question)) { + if (!UIUtils.isQuestionOnlyText(question)) { System.err.println("debug: an element in question is not of TEXT type"); throw new UnsupportedOperationException(); } - System.out.println("Question text: " + bytesToString(question.getQuestion().getData())); - System.out.println("Description: " + bytesToString(question.getDescription().getData())); + System.out.println("Question text: " + UIUtils.bytesToString(question.getQuestion().getData())); + System.out.println("Description: " + UIUtils.bytesToString(question.getDescription().getData())); int answerIndex = 0; for (UIElement answer : question.getAnswerList()) { ++answerIndex; - System.out.println("Answer " + answerIndex + ": " + bytesToString(answer.getData())); + System.out.println("Answer " + answerIndex + ": " + UIUtils.bytesToString(answer.getData())); } } - /** - * checks whether the data of the question is only text. This is the only type we can handle in ConsoleUI - * @param question a ballot question to check - * @return True if the question data is only text - */ - private boolean isQuestionOnlyText (BallotQuestion question) { - boolean isText = true; - if (question.getQuestion().getType() != UIElementDataType.TEXT - || question.getDescription().getType() != UIElementDataType.TEXT) { - isText = false; - } - for (UIElement answer : question.getAnswerList()) { - if (answer.getType() != UIElementDataType.TEXT) { - isText = false; - } - } - return isText; - } - - - private BallotAnswer translateStringAnswerToProtoBufMessageAnswer(String s) { - BallotAnswer.Builder bab = BallotAnswer.newBuilder(); - StringTokenizer st = new StringTokenizer(s); - while (st.hasMoreTokens()) { - bab.addAnswer(Integer.parseInt(st.nextToken())); - } - return bab.build(); - } - - /* - * Returns the UTF8 decoding of byte-string data - */ - private static String bytesToString(ByteString data) { - return data.toStringUtf8(); - } - - private boolean wasShutDownCalled () { return shutDownHasBeenCalled; } diff --git a/voting-booth/src/main/java/meerkat/voting/ui/UIUtils.java b/voting-booth/src/main/java/meerkat/voting/ui/UIUtils.java new file mode 100644 index 0000000..9c723e0 --- /dev/null +++ b/voting-booth/src/main/java/meerkat/voting/ui/UIUtils.java @@ -0,0 +1,80 @@ +package meerkat.voting.ui; + +import com.google.protobuf.ByteString; +import meerkat.protobuf.Voting; +import org.slf4j.Logger; +import java.util.List; +import java.util.StringTokenizer; + +/** + * Created by Vladimir Eliezer Tokarev on 9/17/2016. + * This class contains methods that are used by all the UI's implementations + */ +class UIUtils { + + /** + * Convert string into protobuff message answer + * @param s string represent of an answer for a question + * @return protobuff message answer + */ + static Voting.BallotAnswer translateStringAnswerToProtoBufMessageAnswer(String s) { + Voting.BallotAnswer.Builder bab = Voting.BallotAnswer.newBuilder(); + StringTokenizer st = new StringTokenizer(s); + while (st.hasMoreTokens()) { + bab.addAnswer(Integer.parseInt(st.nextToken())); + } + return bab.build(); + } + + + /** + * checks whether the data of the question is only text. This is the only type we can handle in ConsoleUI + * @param question a ballot question to check + * @return True if the question data is only text + */ + static boolean isQuestionOnlyText(Voting.BallotQuestion question) { + boolean isText = true; + if (question.getQuestion().getType() != Voting.UIElementDataType.TEXT + || question.getDescription().getType() != Voting.UIElementDataType.TEXT) { + isText = false; + } + for (Voting.UIElement answer : question.getAnswerList()) { + if (answer.getType() != Voting.UIElementDataType.TEXT) { + isText = false; + } + } + return isText; + } + + /** + * asserts that the question data matches the types that we can handle in the ConsoleUI + * (better and more sophisticated UI will be able to handle more types of data) + * @param questions list of the questions + * @param logger logger to which writing logs + */ + static void assertQuestionsAreValid (List questions,Logger logger) { + for (int index = 0; index < questions.size(); ++index) { + Voting.BallotQuestion question = questions.get(index); + if (question.getIsMandatory()) { + String errorMessage = "askVoterQuestions: question number " + index + " is marked as mandatory"; + logger.error(errorMessage); + throw new UnsupportedOperationException(errorMessage); + } + if (!UIUtils.isQuestionOnlyText(question)) { + String errorMessage = "askVoterQuestions: question number " + index + " is not only text"; + logger.error(errorMessage); + throw new UnsupportedOperationException(errorMessage); + } + } + } + + /** + * Returns the UTF8 decoding of byte-string data + * @param data string of data + * @return data converted to ut8 str + */ + static String bytesToString(ByteString data) { + return data.toStringUtf8(); + } + +} diff --git a/voting-booth/src/test/java/meerkat/voting/NetworkVirtualPrinterTest.java b/voting-booth/src/test/java/meerkat/voting/NetworkVirtualPrinterTest.java index 80c9b72..ce2c9be 100644 --- a/voting-booth/src/test/java/meerkat/voting/NetworkVirtualPrinterTest.java +++ b/voting-booth/src/test/java/meerkat/voting/NetworkVirtualPrinterTest.java @@ -45,10 +45,6 @@ public class NetworkVirtualPrinterTest { private NetworkVirtualPrinter networkPrinter; - - - - private class ScanHandler implements FutureCallback { private final ScannedData expectedData;