Created the Graphical User Interface

This objhect is the connector between the backed and the voting booth gui
also moved  number of methods from SystemConsoleUI into Utils class for
all the UI's classes be able to use those methods.
android-scanner
Vladimir Eliezer Tokarev 2016-09-17 12:33:40 +03:00
parent c6f2ffff9b
commit 9444072f40
4 changed files with 340 additions and 74 deletions

View File

@ -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<UICommand> 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<Void> 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<Voting.BallotQuestion> questions, FutureCallback<List<Voting.BallotAnswer>> 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<Voting.BallotQuestion> questions, FutureCallback<List<Voting.BallotAnswer>> 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<FinalizeBallotChoice> 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<Void> 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<Boolean> 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<Integer> 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<Voting.BallotAnswer> askVoterForAnswers(List<Voting.BallotQuestion> questions) throws VoterCancelThrowable, IOException {
return new ArrayList<Voting.BallotAnswer>();
}
}

View File

@ -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] = "<NON_TEXT>";
} 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<BallotQuestion> 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<BallotAnswer> askVoterForAnswers(List<BallotQuestion> questions) throws VoterCancelThrowable, IOException {
assertQuestionsAreValid (questions);
UIUtils.assertQuestionsAreValid (questions, logger);
List<BallotAnswer> 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;
}

View File

@ -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<Voting.BallotQuestion> 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();
}
}

View File

@ -45,10 +45,6 @@ public class NetworkVirtualPrinterTest {
private NetworkVirtualPrinter networkPrinter;
private class ScanHandler implements FutureCallback<ScannedData> {
private final ScannedData expectedData;