Skip to content

Commit e41d18e

Browse files
authored
CLDR-18927 non-TC orgs should tally by count, not just latest time (#5248)
1 parent bcccdc3 commit e41d18e

File tree

4 files changed

+139
-26
lines changed

4 files changed

+139
-26
lines changed

tools/cldr-code/src/main/java/org/unicode/cldr/util/Counter.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ private static final class RWLong implements Comparable<RWLong> {
4848
public long value;
4949
private final int forceUnique;
5050
public long time;
51+
public int participants;
5152

5253
{
5354
synchronized (RWLong.class) { // make thread-safe
@@ -71,6 +72,16 @@ public int compareTo(RWLong that) {
7172
public String toString() {
7273
return String.valueOf(value);
7374
}
75+
76+
/** return the number of times participate() was counted */
77+
public final int getParticipants() {
78+
return participants;
79+
}
80+
81+
/** add one to the participant coutn */
82+
void participate() {
83+
participants++;
84+
}
7485
}
7586

7687
public Counter<T> add(T obj, long countValue) {
@@ -85,7 +96,10 @@ public Counter<T> add(T obj, long countValue, long time) {
8596
RWLong count = map.get(obj);
8697
if (count == null) map.put(obj, count = new RWLong());
8798
count.value += countValue;
88-
count.time = time;
99+
if (countValue != 0) {
100+
count.time = time;
101+
}
102+
count.participate();
89103
return this;
90104
}
91105

@@ -106,6 +120,12 @@ public long get(T obj) {
106120
return count == null ? 0 : count.value;
107121
}
108122

123+
/** returns the number of times 'add' was called */
124+
public int getParticipation(T obj) {
125+
RWLong count = map.get(obj);
126+
return count == null ? 0 : count.getParticipants();
127+
}
128+
109129
/**
110130
* Get the time, or 0
111131
*

tools/cldr-code/src/main/java/org/unicode/cldr/util/VoteResolver.java

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -579,8 +579,8 @@ public int hashCode() {
579579
* @param <T>
580580
*/
581581
static class MaxCounter<T> extends Counter<T> {
582-
public MaxCounter(boolean b) {
583-
super(b);
582+
public MaxCounter(boolean naturalOrdering) {
583+
super(naturalOrdering);
584584
}
585585

586586
/** Add, but only to bring up to the maximum value. */
@@ -589,6 +589,8 @@ public MaxCounter<T> add(T obj, long countValue, long time) {
589589
long value = getCount(obj);
590590
if ((value <= countValue)) {
591591
super.add(obj, countValue - value, time); // only add the difference!
592+
} else {
593+
super.add(obj, 0, time); // just add a participant
592594
}
593595
return this;
594596
}
@@ -780,19 +782,23 @@ public Counter<T> getTotals(EnumSet<Organization> conflictedOrganizations) {
780782
}
781783
}
782784
}
783-
// This is deprecated, but preserve it until the method is removed.
784-
/*
785-
* TODO: explain the above comment, and follow through. What is deprecated (orgToAdd, or getOrgVote)?
786-
* Preserve until which method is removed (getOrgVote)?
787-
*/
785+
786+
// temporarily make the top voted value this org's value.
787+
// TODO: may not be needed, see below
788788
orgToAdd.put(org, value);
789789

790-
// We add the max vote for each of the organizations choices
791-
long maxCount = 0;
790+
/** does this org vote by time (TC) or by number of votes (others)? */
791+
final boolean votesByTime = orgVotesByTime(org);
792+
792793
T considerItem = null;
793794
long considerCount = 0;
794-
long maxtime = 0;
795795
long considerTime = 0;
796+
int considerParticipation = 0;
797+
798+
// We add the max vote for each of the organizations choices
799+
long maxCount = 0;
800+
long maxtime = 0;
801+
796802
for (T item : items.keySet()) {
797803
if (DEBUG) {
798804
System.out.println(
@@ -802,6 +808,7 @@ public Counter<T> getTotals(EnumSet<Organization> conflictedOrganizations) {
802808
}
803809
long count = items.getCount(item);
804810
long time = items.getTime(item);
811+
int participation = votesByTime ? 0 : items.getParticipation(item);
805812
if (count > maxCount) {
806813
maxCount = count;
807814
maxtime = time;
@@ -823,9 +830,21 @@ public Counter<T> getTotals(EnumSet<Organization> conflictedOrganizations) {
823830
+ "MAXCOUNT: "
824831
+ maxCount);
825832
}
826-
considerCount = items.getCount(considerItem);
827-
considerTime = items.getTime(considerItem);
828-
} else if ((time > maxtime) && (count == maxCount)) {
833+
considerCount = items.getCount(considerItem); // TODO: == maxCount == count?
834+
considerTime = items.getTime(considerItem); // TODO: == maxtime == time?
835+
} else if (!votesByTime
836+
&& (count == maxCount)
837+
&& (participation > considerParticipation)) {
838+
// tell the 'losing' item
839+
if (considerItem != null) {
840+
annotateTranscript(
841+
"---- Org is not voting for '%s' with %d votes: there is an item '%s' with %d votes",
842+
considerItem, considerParticipation, item, participation);
843+
}
844+
considerItem = item;
845+
} else if ((time > maxtime)
846+
&& (count == maxCount)
847+
&& (votesByTime || (participation == considerParticipation))) {
829848
maxtime = time;
830849
// tell the 'losing' item
831850
if (considerItem != null) {
@@ -844,10 +863,12 @@ public Counter<T> getTotals(EnumSet<Organization> conflictedOrganizations) {
844863
+ new Timestamp(considerTime));
845864
}
846865
}
866+
considerParticipation = participation;
847867
}
848868
annotateTranscript(
849869
"--- %s vote is for '%s' with strength %d",
850870
org.getDisplayName(), considerItem, considerCount);
871+
// TODO: is this ever not reached if there is a value?
851872
orgToAdd.put(org, considerItem);
852873
totals.add(considerItem, considerCount, considerTime);
853874

@@ -906,21 +927,18 @@ public String toString() {
906927
}
907928

908929
/**
909-
* This is now deprecated, since the organization may have multiple votes.
930+
* Get the winning vote for this organization
910931
*
911932
* @param org
912933
* @return
913-
* @deprecated
914934
*/
915-
@Deprecated
916935
public T getOrgVote(Organization org) {
917936
return orgToAdd.get(org);
918937
}
919938

920-
public T getOrgVoteRaw(Organization orgOfUser) {
921-
return orgToAdd.get(orgOfUser);
922-
}
923-
939+
/**
940+
* @return all possible votes from the org, including ones which are disputed.
941+
*/
924942
public Map<T, Long> getOrgToVotes(Organization org) {
925943
Map<T, Long> result = new LinkedHashMap<>();
926944
MaxCounter<T> counter = orgToVotes.get(org);
@@ -1912,17 +1930,36 @@ public EnumSet<Organization> getConflictedOrganizations() {
19121930
* @return
19131931
*/
19141932
public T getOrgVote(Organization org) {
1933+
if (!resolved) {
1934+
resolveVotes();
1935+
}
19151936
return organizationToValueAndVote.getOrgVote(org);
19161937
}
19171938

1939+
/**
1940+
* @return a map of all votes for this organization
1941+
*/
19181942
public Map<T, Long> getOrgToVotes(Organization org) {
1943+
if (!resolved) {
1944+
resolveVotes();
1945+
}
19191946
return organizationToValueAndVote.getOrgToVotes(org);
19201947
}
19211948

19221949
public Map<String, Long> getNameTime() {
1950+
if (!resolved) {
1951+
resolveVotes();
1952+
}
19231953
return organizationToValueAndVote.getNameTime();
19241954
}
19251955

1956+
/**
1957+
* @return true if this organization's votes are ordered by time (as with TC orgs)
1958+
*/
1959+
private static boolean orgVotesByTime(Organization org) {
1960+
return (org.isTCOrg());
1961+
}
1962+
19261963
/**
19271964
* Get a String representation of this VoteResolver. This is sent to the client as
19281965
* "voteResolver.raw" and is used only for debugging.
@@ -2172,7 +2209,7 @@ public VoteStatus getStatusForOrganization(Organization orgOfUser) {
21722209
// If the value is provisional, it needs more votes.
21732210
return VoteStatus.provisionalOrWorse;
21742211
}
2175-
T orgVote = organizationToValueAndVote.getOrgVoteRaw(orgOfUser);
2212+
T orgVote = organizationToValueAndVote.getOrgVote(orgOfUser);
21762213
if (!equalsOrgVote(winningValue, orgVote)) {
21772214
// We voted and lost
21782215
return VoteStatus.losing;

tools/cldr-code/src/test/java/org/unicode/cldr/unittest/TestHelper.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,10 @@ public enum TestUser {
316316
microsoftV(134, Organization.microsoft, Level.vetter),
317317
ibmE(114, Organization.ibm, Level.manager),
318318
ibmT(129, Organization.ibm, Level.tc),
319-
unaffiliatedS2(802, Organization.unaffiliated, Level.guest);
319+
unaffiliatedS2(802, Organization.unaffiliated, Level.guest),
320+
bretonV1(901, Organization.breton, Level.vetter),
321+
bretonV2(902, Organization.breton, Level.vetter),
322+
bretonV3(903, Organization.breton, Level.vetter);
320323

321324
public static final Map<Integer, VoterInfo> TEST_USERS;
322325
public final Integer voterId;

tools/cldr-code/src/test/java/org/unicode/cldr/util/TestVoteResolver.java

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,56 @@
1919
*/
2020
public class TestVoteResolver {
2121

22+
@Test
23+
void testNonTcOrgByCountNotTime() {
24+
final VoteResolver<String> vr = getStringResolver();
25+
vr.enableTranscript();
26+
vr.setLocale(
27+
CLDRLocale.getInstance("br"), null); // NB: pathHeader is needed for annotations
28+
vr.setBaseline("Inez Bouvet", Status.unconfirmed);
29+
vr.setBaileyValue("BV");
30+
31+
// Three dates
32+
final Date t0 = new Date(1500000000000L);
33+
final Date t1 = new Date(1600000000000L);
34+
final Date t2 = new Date(1700000000000L);
35+
36+
// earlier, but majority vote
37+
vr.add("1 Bouvet", TestHelper.TestUser.bretonV1.voterId, null, t0);
38+
vr.add("1 Bouvet", TestHelper.TestUser.bretonV2.voterId, null, t1);
39+
// latest, but minority vote
40+
vr.add("2 Bouvet", TestHelper.TestUser.bretonV3.voterId, null, t2);
41+
42+
boolean printTranscript = true;
43+
try {
44+
assertAll(
45+
"Verify org's vote",
46+
() ->
47+
assertEquals(
48+
"1 Bouvet",
49+
vr.getOrgVote(Organization.breton),
50+
"breton's vote"),
51+
() ->
52+
assertEquals(
53+
4,
54+
vr.getOrgToVotes(Organization.breton).get("1 Bouvet"),
55+
"breton's vote for '1 Bouvet'"),
56+
() -> assertEquals("1 Bouvet", vr.getWinningValue(), "for the winning value"),
57+
() ->
58+
assertEquals(
59+
VoteResolver.VoteStatus.ok,
60+
vr.getStatusForOrganization(Organization.breton)));
61+
printTranscript = false; // none of the assertions failed, so no need to print
62+
} finally {
63+
if (printTranscript) {
64+
// if there were errors, print out the transcript for debugging.
65+
vr.isDisputed(); // to cause calculation
66+
System.err.println(vr.getTranscript());
67+
System.err.println(vr.toString());
68+
}
69+
}
70+
}
71+
2272
@Test
2373
void testDisputed() {
2474
final VoteResolver<String> vr = getStringResolver();
@@ -95,16 +145,19 @@ void testExplanations() {
95145
vr.add("bafut", TestHelper.TestUser.unaffiliatedS.voterId);
96146

97147
vr.enableTranscript(); // Should be recalculated from here.
148+
final String vrString = vr.toString(); // NB: toString() modifies the transcript!
98149
assertAll(
99150
"Verify the outcome",
100151
() -> assertEquals("bambara", vr.getWinningValue()),
101152
() -> assertEquals(Status.provisional, vr.getWinningStatus()));
102153
final String transcriptText = vr.getTranscript();
103-
System.out.println(transcriptText);
104-
System.out.println(vr.toString()); // NB: toString() modifies the transcript!
105154
assertTrue(
106155
transcriptText.contains("earlier than 'bassa'"),
107-
() -> "Transcript did not match expectations:\n" + transcriptText);
156+
() ->
157+
"Transcript did not match expectations:\n"
158+
+ transcriptText
159+
+ "\n"
160+
+ vrString);
108161
}
109162

110163
private VoteResolver<String> getStringResolver() {

0 commit comments

Comments
 (0)