fb7415cd599e3b237da8a386b9de04e131e08e6d
[cacert-votebot.git] / src / main / java / org / cacert / votebot / shared / CAcertVoteMechanics.java
1 /*
2 * Copyright (c) 2015 Felix Doerre
3 * Copyright (c) 2015 Benny Baumann
4 * Copyright (c) 2016, 2018 Jan Dittberner
5 *
6 * This file is part of CAcert VoteBot.
7 *
8 * CAcert VoteBot is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the Free
10 * Software Foundation, either version 3 of the License, or (at your option)
11 * any later version.
12 *
13 * CAcert VoteBot is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 * more details.
17 *
18 * You should have received a copy of the GNU General Public License along with
19 * CAcert VoteBot. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 package org.cacert.votebot.shared;
23
24 import org.springframework.stereotype.Component;
25
26 import java.text.MessageFormat;
27 import java.util.Calendar;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.ResourceBundle;
32 import java.util.regex.Pattern;
33
34 /**
35 * Represents the voting-automate for voting in IRC channels.
36 */
37 @Component
38 public class CAcertVoteMechanics {
39 private static final Pattern PROXY_RE = Pattern.compile("^\\s*proxy\\s.*");
40 private static final int VOTE_MESSAGE_PART_COUNT = 3;
41
42 private State state = State.IDLE;
43 private String topic;
44 private final Map<String, VoteType> votes = new HashMap<>();
45 private final ResourceBundle messages = ResourceBundle.getBundle("messages");
46
47 public Calendar getWarnTime() {
48 return warnTime;
49 }
50
51 public Calendar getEndTime() {
52 return endTime;
53 }
54
55 private Calendar warnTime;
56 private Calendar endTime;
57 private boolean warned;
58
59 public boolean isWarned() {
60 return warned;
61 }
62
63 public synchronized void setWarned() {
64 this.warned = true;
65 }
66
67 /**
68 * Voting state indicating whether a vote is currently running or not.
69 */
70 public enum State {
71 /**
72 * No vote is running.
73 */
74 IDLE,
75 /**
76 * A vote is currently running.
77 */
78 RUNNING,
79 /**
80 * A vote is about to stop.
81 */
82 STOPPING
83 }
84
85 private String vote(final String voter, final String actor, final VoteType type) {
86 votes.put(voter, type);
87
88 if (voter.equals(actor)) {
89 return MessageFormat.format(messages.getString("count_vote"), actor, type);
90 } else {
91 return MessageFormat.format(messages.getString("count_proxy_vote"), actor, voter, type);
92 }
93 }
94
95 private String voteError(final String actor) {
96 return MessageFormat.format(messages.getString("vote_not_understood"), actor);
97 }
98
99 private String proxyVoteError(final String actor) {
100 return MessageFormat.format(messages.getString("invalid_proxy_vote"), actor);
101 }
102
103 /**
104 * Adds a vote to the current topic. This interprets proxies.
105 *
106 * @param actor the person that sent this vote
107 * @param txt the text that the person sent.
108 * @return A message to <code>actor</code> indicating the result of his action.
109 */
110 public synchronized String evaluateVote(final String actor, final String txt) {
111 if (state != State.RUNNING) {
112 return MessageFormat.format(messages.getString("no_vote_running"), actor);
113 }
114
115 final String voter;
116 final String value;
117
118 if (PROXY_RE.matcher(txt.toLowerCase()).matches()) {
119 String[] parts = txt.split("\\s+");
120 if (parts.length == VOTE_MESSAGE_PART_COUNT) {
121 voter = parts[1];
122 value = parts[2];
123 } else {
124 return proxyVoteError(actor);
125 }
126 } else {
127 voter = actor;
128 value = txt.trim();
129 }
130
131 try {
132 return vote(voter, actor, VoteType.evaluate(value));
133 } catch (IllegalArgumentException iae) {
134 return voteError(actor);
135 }
136 }
137
138 /**
139 * A new vote begins.
140 *
141 * @param topic the topic of the vote
142 * @param warn seconds before the end of the vote to issue a warning
143 * @param timeout seconds from the current time to the end of the vote
144 * @return A response to <code>from</code> indicating success or failure.
145 */
146 public synchronized String callVote(final String topic, long warn, long timeout) {
147 if (state != State.IDLE) {
148 return messages.getString("vote_running");
149 }
150
151 this.topic = topic;
152 votes.clear();
153
154 this.warnTime = Calendar.getInstance();
155 this.warnTime.add(Calendar.SECOND, Math.toIntExact(timeout - warn));
156 this.warned = false;
157
158 this.endTime = Calendar.getInstance();
159 this.endTime.add(Calendar.SECOND, Math.toIntExact(timeout));
160
161 state = State.RUNNING;
162
163 return messages.getString("vote_started");
164 }
165
166 public synchronized String stopVote(String stopSource) {
167 if (state != State.RUNNING) {
168 throw new IllegalStateException(messages.getString("no_vote_running_private"));
169 }
170
171 state = State.STOPPING;
172
173 return MessageFormat.format(messages.getString("finishing_vote"), this.topic, stopSource);
174 }
175
176 /**
177 * Ends a vote.
178 *
179 * @return An array of Strings containing result status messages.
180 */
181 public synchronized String[] closeVote() {
182 final String[] results = new String[VoteType.values().length];
183
184 if (state != State.STOPPING) {
185 throw new IllegalStateException(messages.getString("cannot_close_running_vote"));
186 }
187
188 final int[] resultCounts = new int[VoteType.values().length];
189
190 for (final Entry<String, VoteType> voteEntry : votes.entrySet()) {
191 resultCounts[voteEntry.getValue().ordinal()]++;
192 }
193
194 for (int i = 0; i < results.length; i++) {
195 results[i] = MessageFormat.format("{0}: {1}", VoteType.values()[i], resultCounts[i]);
196 }
197
198 votes.clear();
199 state = State.IDLE;
200 topic = "";
201
202 return results;
203 }
204
205 /**
206 * @return Topic of the current vote.
207 */
208 public String getTopic() {
209 return topic;
210 }
211
212 /**
213 * @return Voting state
214 */
215 public State getState() {
216 return state;
217 }
218
219 /**
220 * @return current vote results as string
221 */
222 public String getCurrentResult() {
223 return votes.toString();
224 }
225
226 }