mirror of
https://github.com/ArenMg/aren.git
synced 2024-11-21 16:10:52 +00:00
commit
e0db6dac3f
34 changed files with 2491 additions and 2 deletions
|
@ -25,6 +25,8 @@ import java.time.ZonedDateTime;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
import javax.persistence.JoinTable;
|
import javax.persistence.JoinTable;
|
||||||
|
|
||||||
|
import fr.lirmm.aren.model.vm.VMTeam;
|
||||||
import org.hibernate.annotations.Filter;
|
import org.hibernate.annotations.Filter;
|
||||||
import org.hibernate.annotations.Filters;
|
import org.hibernate.annotations.Filters;
|
||||||
import org.hibernate.annotations.SortNatural;
|
import org.hibernate.annotations.SortNatural;
|
||||||
|
@ -135,6 +137,15 @@ public class User extends AbstractEntEntity implements Serializable {
|
||||||
@SortNatural
|
@SortNatural
|
||||||
private SortedSet<Team> teams = new TreeSet<>();
|
private SortedSet<Team> teams = new TreeSet<>();
|
||||||
|
|
||||||
|
@JoinTable(name = "vm_teams_users",
|
||||||
|
joinColumns = {
|
||||||
|
@JoinColumn(name = "user_id", referencedColumnName = "id")},
|
||||||
|
inverseJoinColumns = {
|
||||||
|
@JoinColumn(name = "team_id", referencedColumnName = "id")})
|
||||||
|
@ManyToMany
|
||||||
|
@SortNatural
|
||||||
|
private SortedSet<VMTeam> vmTeams = new TreeSet<>();
|
||||||
|
|
||||||
@OneToMany(mappedBy = "owner")
|
@OneToMany(mappedBy = "owner")
|
||||||
@SortNatural
|
@SortNatural
|
||||||
private SortedSet<Notification> notifications = new TreeSet<>();
|
private SortedSet<Notification> notifications = new TreeSet<>();
|
||||||
|
@ -372,6 +383,40 @@ public class User extends AbstractEntEntity implements Serializable {
|
||||||
team.getUsers().remove(this);
|
team.getUsers().remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public SortedSet<VMTeam> getVmTeams() {
|
||||||
|
return vmTeams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param vmTeams
|
||||||
|
*/
|
||||||
|
public void setVmTeams(SortedSet<VMTeam> vmTeams) {
|
||||||
|
this.vmTeams = vmTeams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param team
|
||||||
|
*/
|
||||||
|
public void addMember(VMTeam team) {
|
||||||
|
vmTeams.add(team);
|
||||||
|
team.getMembers().add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param team
|
||||||
|
*/
|
||||||
|
public void removeMember(VMTeam team) {
|
||||||
|
vmTeams.remove(team);
|
||||||
|
team.getMembers().remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
|
|
311
src/main/java/fr/lirmm/aren/model/vm/VMChoice.java
Normal file
311
src/main/java/fr/lirmm/aren/model/vm/VMChoice.java
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
package fr.lirmm.aren.model.vm;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||||
|
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||||
|
import fr.lirmm.aren.model.AbstractEntity;
|
||||||
|
import org.hibernate.annotations.SortNatural;
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 24/06/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "vm_choices")
|
||||||
|
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = VMChoice.class)
|
||||||
|
public class VMChoice extends AbstractEntity implements Serializable {
|
||||||
|
|
||||||
|
@JoinColumn(name = "themeId", referencedColumnName = "id")
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
private VMTheme themeId;
|
||||||
|
|
||||||
|
@Size(max = 255)
|
||||||
|
@Column(name = "title")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Type(type = "org.hibernate.type.TextType")
|
||||||
|
@Column(name = "description")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Size(max = 1024)
|
||||||
|
@Column(name = "url")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Type(type = "org.hibernate.type.TextType")
|
||||||
|
@Column(name = "img")
|
||||||
|
private String img;
|
||||||
|
|
||||||
|
@Column(name = "rejected")
|
||||||
|
private int rejected=0;
|
||||||
|
|
||||||
|
@Column(name = "insufficient")
|
||||||
|
private int insufficient=0;
|
||||||
|
|
||||||
|
@Column(name = "pass")
|
||||||
|
private int pass=0;
|
||||||
|
|
||||||
|
@Column(name = "acceptable")
|
||||||
|
private int acceptable=0;
|
||||||
|
|
||||||
|
@Column(name = "good")
|
||||||
|
private int good=0;
|
||||||
|
|
||||||
|
@Column(name = "very_good")
|
||||||
|
private int veryGood=0;
|
||||||
|
|
||||||
|
@Column(name = "excellent")
|
||||||
|
private int excellent=0;
|
||||||
|
|
||||||
|
@Column(name = "createdAt")
|
||||||
|
private ZonedDateTime createdAt=ZonedDateTime.now();
|
||||||
|
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "subThemeId",fetch = FetchType.LAZY)
|
||||||
|
@SortNatural
|
||||||
|
private SortedSet<VMVote> votes = new TreeSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public VMTheme getThemeId() {
|
||||||
|
return themeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param themeId
|
||||||
|
*/
|
||||||
|
public void setThemeId(VMTheme themeId) {
|
||||||
|
this.themeId = themeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param title
|
||||||
|
*/
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param description
|
||||||
|
*/
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
*/
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getImg() {
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param img
|
||||||
|
*/
|
||||||
|
public void setImg(String img) {
|
||||||
|
this.img = img;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getRejected() {
|
||||||
|
return rejected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param rejected
|
||||||
|
*/
|
||||||
|
public void setRejected(int rejected) {
|
||||||
|
this.rejected = rejected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getInsufficient() {
|
||||||
|
return insufficient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param insufficient
|
||||||
|
*/
|
||||||
|
public void setInsufficient(int insufficient) {
|
||||||
|
this.insufficient = insufficient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getPass() {
|
||||||
|
return pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param pass
|
||||||
|
*/
|
||||||
|
public void setPass(int pass) {
|
||||||
|
this.pass = pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getAcceptable() {
|
||||||
|
return acceptable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param acceptable
|
||||||
|
*/
|
||||||
|
public void setAcceptable(int acceptable) {
|
||||||
|
this.acceptable = acceptable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getGood() {
|
||||||
|
return good;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param good
|
||||||
|
*/
|
||||||
|
public void setGood(int good) {
|
||||||
|
this.good = good;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getVeryGood() {
|
||||||
|
return veryGood;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param veryGood
|
||||||
|
*/
|
||||||
|
public void setVeryGood(int veryGood) {
|
||||||
|
this.veryGood = veryGood;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getExcellent() {
|
||||||
|
return excellent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param excellent
|
||||||
|
*/
|
||||||
|
public void setExcellent(int excellent) {
|
||||||
|
this.excellent = excellent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param createdAt
|
||||||
|
*/
|
||||||
|
public void setCreatedAt(ZonedDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public SortedSet<VMVote> getVotes() {
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param votes
|
||||||
|
*/
|
||||||
|
public void setVotes(SortedSet<VMVote> votes) {
|
||||||
|
this.votes = votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVoted(){
|
||||||
|
if(this.getRejected()!=0 ||
|
||||||
|
this.getInsufficient()!=0 ||
|
||||||
|
this.getPass()!=0 ||
|
||||||
|
this.getAcceptable()!=0 ||
|
||||||
|
this.getGood()!=0 ||
|
||||||
|
this.getVeryGood()!=0 ||
|
||||||
|
this.getExcellent()!=0
|
||||||
|
) return true ;
|
||||||
|
return false ;
|
||||||
|
}
|
||||||
|
}
|
81
src/main/java/fr/lirmm/aren/model/vm/VMTeam.java
Normal file
81
src/main/java/fr/lirmm/aren/model/vm/VMTeam.java
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package fr.lirmm.aren.model.vm;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||||
|
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||||
|
import fr.lirmm.aren.model.AbstractEntity;
|
||||||
|
import fr.lirmm.aren.model.User;
|
||||||
|
import org.hibernate.annotations.SortNatural;
|
||||||
|
import org.hibernate.annotations.Where;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 11/07/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "vm_teams")
|
||||||
|
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = VMTheme.class)
|
||||||
|
public class VMTeam extends AbstractEntity implements Serializable {
|
||||||
|
@Size(max = 255)
|
||||||
|
@Column(name = "name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ManyToMany(mappedBy = "vmTeams")
|
||||||
|
@SortNatural
|
||||||
|
private SortedSet<User> members = new TreeSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public SortedSet<User> getMembers() {
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param members
|
||||||
|
*/
|
||||||
|
public void setMembers(SortedSet<User> members) {
|
||||||
|
this.members = members;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
*/
|
||||||
|
public void addUser(User user) {
|
||||||
|
this.members.add(user);
|
||||||
|
user.getVmTeams().add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
*/
|
||||||
|
public void removeUser(User user) {
|
||||||
|
this.members.remove(user);
|
||||||
|
user.getVmTeams().remove(this);
|
||||||
|
}
|
||||||
|
}
|
173
src/main/java/fr/lirmm/aren/model/vm/VMTheme.java
Normal file
173
src/main/java/fr/lirmm/aren/model/vm/VMTheme.java
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
package fr.lirmm.aren.model.vm;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||||
|
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||||
|
import fr.lirmm.aren.model.AbstractEntity;
|
||||||
|
import fr.lirmm.aren.model.Team;
|
||||||
|
import fr.lirmm.aren.model.User;
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 24/06/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "vm_themes")
|
||||||
|
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = VMTheme.class)
|
||||||
|
public class VMTheme extends AbstractEntity implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@JoinColumn(name = "author", referencedColumnName = "id")
|
||||||
|
@ManyToOne
|
||||||
|
private User author;
|
||||||
|
|
||||||
|
@JoinColumn(name = "team", referencedColumnName = "id")
|
||||||
|
@ManyToOne
|
||||||
|
private Team team;
|
||||||
|
|
||||||
|
@Size(max = 255)
|
||||||
|
@Column(name = "title")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Type(type = "org.hibernate.type.TextType")
|
||||||
|
@Column(name = "description")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Column(name = "createdAt")
|
||||||
|
private ZonedDateTime createdAt=ZonedDateTime.now();
|
||||||
|
|
||||||
|
@Column(name = "expiracyDate")
|
||||||
|
private ZonedDateTime expiracyDate;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "themeId")
|
||||||
|
private Set<VMChoice> choices = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public User getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param author
|
||||||
|
*/
|
||||||
|
public void setAuthor(User author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Team getTeam() {
|
||||||
|
return team;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param team
|
||||||
|
*/
|
||||||
|
public void setTeam(Team team) {
|
||||||
|
this.team = team;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param title
|
||||||
|
*/
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param description
|
||||||
|
*/
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param createdAt
|
||||||
|
*/
|
||||||
|
public void setCreatedAt(ZonedDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getExpiracyDate() {
|
||||||
|
return expiracyDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param expiracyDate
|
||||||
|
*/
|
||||||
|
public void setExpiracyDate(ZonedDateTime expiracyDate) {
|
||||||
|
this.expiracyDate = expiracyDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Set<VMChoice> getChoices() {
|
||||||
|
return choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param choices
|
||||||
|
*/
|
||||||
|
public void setChoices(Set<VMChoice> choices) {
|
||||||
|
this.choices = choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
return "title : "+this.title+"\n"
|
||||||
|
+"Description : "+this.description+"\n"
|
||||||
|
+"Expiration : "+this.expiracyDate+"\n";
|
||||||
|
}
|
||||||
|
}
|
108
src/main/java/fr/lirmm/aren/model/vm/VMVote.java
Normal file
108
src/main/java/fr/lirmm/aren/model/vm/VMVote.java
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package fr.lirmm.aren.model.vm;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||||
|
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||||
|
import fr.lirmm.aren.model.AbstractEntity;
|
||||||
|
import fr.lirmm.aren.model.User;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 25/06/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "vm_votes")
|
||||||
|
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = VMVote.class)
|
||||||
|
public class VMVote extends AbstractEntity implements Serializable {
|
||||||
|
public enum Opinion {
|
||||||
|
REJECTED,
|
||||||
|
INSUFFICIENT,
|
||||||
|
PASS,
|
||||||
|
ACCEPTABLE,
|
||||||
|
GOOD,
|
||||||
|
VERY_GOOD,
|
||||||
|
EXCELLENT
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "subThemeId")
|
||||||
|
private VMChoice subThemeId;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "authorId")
|
||||||
|
private User authorId ;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "opinion", nullable = false)
|
||||||
|
private Opinion opinion=Opinion.REJECTED;
|
||||||
|
|
||||||
|
@Column(name = "createdAt")
|
||||||
|
private ZonedDateTime createdAt=ZonedDateTime.now();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public VMChoice getSubThemeId() {
|
||||||
|
return subThemeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param subThemeId
|
||||||
|
*/
|
||||||
|
public void setSubThemeId(VMChoice subThemeId) {
|
||||||
|
this.subThemeId = subThemeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public User getAuthorId() {
|
||||||
|
return authorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param authorId
|
||||||
|
*/
|
||||||
|
public void setAuthorId(User authorId) {
|
||||||
|
this.authorId = authorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Opinion getOpinion() {
|
||||||
|
return opinion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param opinion
|
||||||
|
*/
|
||||||
|
public void setOpinion(Opinion opinion) {
|
||||||
|
this.opinion = opinion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ZonedDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param createdAt
|
||||||
|
*/
|
||||||
|
public void setCreatedAt(ZonedDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
}
|
|
@ -152,6 +152,42 @@ public abstract class AbstractService<T extends AbstractEntity> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public T insert(T entity) {
|
||||||
|
// If ever the entity is attached, it needs to be detached
|
||||||
|
getEntityManager().detach(entity);
|
||||||
|
// If ever the id is set, it needs to be removed
|
||||||
|
entity.setId(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.transactionBegin();
|
||||||
|
getEntityManager().persist(entity);
|
||||||
|
getEntityManager().refresh(entity);
|
||||||
|
this.afterCreate(entity);
|
||||||
|
this.commit();
|
||||||
|
} catch (PersistenceException e) {
|
||||||
|
if (e.getCause() instanceof PropertyValueException) {
|
||||||
|
PropertyValueException cause = (PropertyValueException) e.getCause();
|
||||||
|
throw InsertEntityException.MANDATORY_PROPERTY(cause.getPropertyName());
|
||||||
|
} else if (e.getCause() instanceof ConstraintViolationException) {
|
||||||
|
// Parse the error to a client readable one
|
||||||
|
Throwable cause = e.getCause().getCause();
|
||||||
|
String details = cause.getMessage();
|
||||||
|
Pattern pattern = Pattern.compile("\\((.*)\\)=\\((.*)\\)");
|
||||||
|
Matcher matcher = pattern.matcher(details);
|
||||||
|
if (matcher.find()) {
|
||||||
|
String keyName = matcher.group(1);
|
||||||
|
String keyValue = matcher.group(2);
|
||||||
|
throw InsertEntityException.DUPLICATE_KEY(keyName, keyValue);
|
||||||
|
} else {
|
||||||
|
throw InsertEntityException.OTHER(details);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entity ;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param entity
|
* @param entity
|
||||||
|
|
|
@ -40,8 +40,8 @@ public class FDThemeService extends AbstractService<FDTheme> {
|
||||||
super.edit(fdTheme);
|
super.edit(fdTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FDTheme find(Long fdPollId, boolean withChoices, boolean withVotes) {
|
public FDTheme find(Long themeId, boolean withChoices, boolean withVotes) {
|
||||||
List<FDTheme> results = generateQuery(fdPollId, withChoices,withVotes).getResultList();
|
List<FDTheme> results = generateQuery(themeId, withChoices,withVotes).getResultList();
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
67
src/main/java/fr/lirmm/aren/service/vm/VMChoiceService.java
Normal file
67
src/main/java/fr/lirmm/aren/service/vm/VMChoiceService.java
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package fr.lirmm.aren.service.vm;
|
||||||
|
|
||||||
|
import fr.lirmm.aren.model.vm.VMChoice;
|
||||||
|
import fr.lirmm.aren.service.AbstractService;
|
||||||
|
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 08/07/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class VMChoiceService extends AbstractService<VMChoice> {
|
||||||
|
public VMChoiceService(){super(VMChoice.class) ;}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param choiceId
|
||||||
|
* @param withVotes
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private TypedQuery<VMChoice> generateQuery(Long choiceId, boolean withVotes){
|
||||||
|
TypedQuery<VMChoice> query = getEntityManager().createQuery("SELECT vmc "
|
||||||
|
+"FROM VMChoice vmc "
|
||||||
|
+(withVotes? "LEFT JOIN FETCH vmc.votes vo " : "")
|
||||||
|
+ (choiceId != null
|
||||||
|
? "WHERE vmc.id = :choiceId "
|
||||||
|
: "WHERE vmc.id IS NOT NULL ")
|
||||||
|
,VMChoice.class
|
||||||
|
) ;
|
||||||
|
if (choiceId != null) {
|
||||||
|
query.setParameter("choiceId", choiceId);
|
||||||
|
}
|
||||||
|
return query ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param withVotes
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Set<VMChoice> findAll(boolean withVotes){
|
||||||
|
return new HashSet<VMChoice>(generateQuery(null, withVotes).getResultList()) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param choice
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void edit(VMChoice choice){
|
||||||
|
super.edit(choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VMChoice find(Long choiceId, boolean withVotes){
|
||||||
|
List<VMChoice> choices=generateQuery(choiceId,withVotes).getResultList() ;
|
||||||
|
if (choices.isEmpty()) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
return choices.get(0) ;
|
||||||
|
}
|
||||||
|
}
|
94
src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java
Normal file
94
src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package fr.lirmm.aren.service.vm;
|
||||||
|
|
||||||
|
import fr.lirmm.aren.model.Debate;
|
||||||
|
import fr.lirmm.aren.model.Team;
|
||||||
|
import fr.lirmm.aren.model.vm.VMTeam;
|
||||||
|
import fr.lirmm.aren.service.AbstractService;
|
||||||
|
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 11/07/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class VMTeamService extends AbstractService<VMTeam> {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public VMTeamService(){
|
||||||
|
super(VMTeam.class) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param teamId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private TypedQuery<VMTeam> generateQuery(Long teamId){
|
||||||
|
TypedQuery<VMTeam> query = getEntityManager().createQuery("SELECT vmt "
|
||||||
|
+"FROM VMTeam vmt "
|
||||||
|
+"LEFT JOIN vmt.members m "
|
||||||
|
+ (teamId != null
|
||||||
|
? "WHERE vmt.id = :teamId "
|
||||||
|
: "WHERE vmt.id IS NOT NULL ")
|
||||||
|
, VMTeam.class
|
||||||
|
) ;
|
||||||
|
if (teamId != null) {
|
||||||
|
query.setParameter("teamId", teamId);
|
||||||
|
}
|
||||||
|
return query ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Set<VMTeam> findAll() {
|
||||||
|
return new HashSet<VMTeam>(generateQuery(null).getResultList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param team
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void edit(VMTeam team) {
|
||||||
|
super.edit(team);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VMTeam insert(VMTeam team) {
|
||||||
|
return super.insert(team) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param teamId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public VMTeam find(Long teamId) {
|
||||||
|
List<VMTeam> teams = generateQuery(teamId).getResultList();
|
||||||
|
if (teams.isEmpty()) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
return teams.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateExternaleTables(VMTeam team) {
|
||||||
|
super.transactionBegin();
|
||||||
|
team.getMembers().forEach(member->{
|
||||||
|
System.out.println(team.getId()+" --- "+member.getId());
|
||||||
|
getEntityManager().createNativeQuery("INSERT INTO vm_teams_users(vmteam_id,members_id) VALUES(?,?) ")
|
||||||
|
.setParameter(1, team.getId())
|
||||||
|
.setParameter(2, member.getId())
|
||||||
|
.executeUpdate();
|
||||||
|
super.commit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
100
src/main/java/fr/lirmm/aren/service/vm/VMThemeService.java
Normal file
100
src/main/java/fr/lirmm/aren/service/vm/VMThemeService.java
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package fr.lirmm.aren.service.vm;
|
||||||
|
|
||||||
|
import fr.lirmm.aren.model.vm.VMChoice;
|
||||||
|
import fr.lirmm.aren.model.vm.VMTheme;
|
||||||
|
import fr.lirmm.aren.service.AbstractService;
|
||||||
|
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 08/07/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class VMThemeService extends AbstractService<VMTheme> {
|
||||||
|
public VMThemeService(){
|
||||||
|
super(VMTheme.class) ;
|
||||||
|
}
|
||||||
|
private TypedQuery<VMTheme> generateQuery(Long themeId, boolean withChoices, boolean withVotes){
|
||||||
|
TypedQuery<VMTheme> query = getEntityManager().createQuery("SELECT vmt "
|
||||||
|
+"FROM VMTheme vmt "
|
||||||
|
+(withChoices? "LEFT JOIN FETCH vmt.choices co " : "")
|
||||||
|
+(withVotes? "LEFT JOIN FETCH co.votes vo " : "")
|
||||||
|
+ (themeId != null
|
||||||
|
? "WHERE vmt.id = :themeId "
|
||||||
|
: "WHERE vmt.id IS NOT NULL ")
|
||||||
|
, VMTheme.class
|
||||||
|
) ;
|
||||||
|
if (themeId != null) {
|
||||||
|
query.setParameter("themeId", themeId);
|
||||||
|
}
|
||||||
|
return query ;
|
||||||
|
}
|
||||||
|
public Set<VMTheme> findAll(boolean withChoices, boolean withVotes) {
|
||||||
|
return new HashSet<VMTheme>(generateQuery(null, withChoices,withVotes).getResultList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void edit(VMTheme theme) {
|
||||||
|
super.edit(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VMTheme find(Long themeId, boolean withChoices, boolean withVotes) {
|
||||||
|
List<VMTheme> themes = generateQuery(themeId, withChoices,withVotes).getResultList();
|
||||||
|
if (themes.isEmpty()) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
return themes.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param themeId
|
||||||
|
*/
|
||||||
|
public void delete(Long themeId) {
|
||||||
|
this.transactionBegin();
|
||||||
|
List<VMChoice> choices = getEntityManager().createQuery("SELECT c "
|
||||||
|
+ "FROM VMChoice c "
|
||||||
|
+ "WHERE c.themeId.id = :themeId ", VMChoice.class)
|
||||||
|
.setParameter("themeId", themeId)
|
||||||
|
.getResultList();
|
||||||
|
if (!choices.isEmpty()) {
|
||||||
|
for(VMChoice choice : choices){
|
||||||
|
this.deleteVotes(choice.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
getEntityManager().createQuery("DELETE FROM VMChoice choice "
|
||||||
|
+ "WHERE choice.themeId.id = :themeId")
|
||||||
|
.setParameter("themeId", themeId)
|
||||||
|
.executeUpdate();
|
||||||
|
|
||||||
|
getEntityManager().createQuery("DELETE FROM VMTheme theme "
|
||||||
|
+ "WHERE theme.id = :themeId")
|
||||||
|
.setParameter("themeId", themeId)
|
||||||
|
.executeUpdate();
|
||||||
|
}catch(Exception ex){
|
||||||
|
System.err.println("Erreur : "+ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param choiceId
|
||||||
|
*/
|
||||||
|
public void deleteVotes(Long choiceId) {
|
||||||
|
this.transactionBegin();
|
||||||
|
getEntityManager().createQuery("DELETE FROM VMVote vote "
|
||||||
|
+ "WHERE vote.subThemeId.id = :choiceId")
|
||||||
|
.setParameter("choiceId", choiceId)
|
||||||
|
.executeUpdate();
|
||||||
|
this.commit();
|
||||||
|
}
|
||||||
|
}
|
86
src/main/java/fr/lirmm/aren/service/vm/VMVoteService.java
Normal file
86
src/main/java/fr/lirmm/aren/service/vm/VMVoteService.java
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package fr.lirmm.aren.service.vm;
|
||||||
|
|
||||||
|
import fr.lirmm.aren.model.vm.VMChoice;
|
||||||
|
import fr.lirmm.aren.model.vm.VMVote;
|
||||||
|
import fr.lirmm.aren.service.AbstractService;
|
||||||
|
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 25/06/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class VMVoteService extends AbstractService<VMVote> {
|
||||||
|
public VMVoteService(){
|
||||||
|
super(VMVote.class) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterCreate(VMVote entity){
|
||||||
|
this.updateExternaleTables(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateExternaleTables(VMVote vmVote){
|
||||||
|
super.transactionBegin();
|
||||||
|
|
||||||
|
List<VMChoice> results = getEntityManager().createQuery("SELECT c "
|
||||||
|
+ "FROM VMChoice c "
|
||||||
|
+ "WHERE c.id = :id", VMChoice.class)
|
||||||
|
.setParameter("id", vmVote.getSubThemeId().getId())
|
||||||
|
.getResultList();
|
||||||
|
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
VMChoice choice=results.get(0) ;
|
||||||
|
switch(vmVote.getOpinion()){
|
||||||
|
case REJECTED:
|
||||||
|
choice.setRejected(choice.getRejected()+1);
|
||||||
|
break ;
|
||||||
|
case INSUFFICIENT:
|
||||||
|
choice.setInsufficient(choice.getInsufficient()+1);
|
||||||
|
break;
|
||||||
|
case PASS:
|
||||||
|
choice.setPass(choice.getPass()+1);
|
||||||
|
break;
|
||||||
|
case ACCEPTABLE:
|
||||||
|
choice.setAcceptable(choice.getAcceptable()+1);
|
||||||
|
break ;
|
||||||
|
case GOOD:
|
||||||
|
choice.setGood(choice.getGood()+1);
|
||||||
|
break ;
|
||||||
|
case VERY_GOOD:
|
||||||
|
choice.setVeryGood(choice.getVeryGood()+1);
|
||||||
|
break ;
|
||||||
|
case EXCELLENT:
|
||||||
|
choice.setExcellent(choice.getExcellent()+1);
|
||||||
|
break ;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntityManager().createQuery("UPDATE VMChoice c SET "
|
||||||
|
+ "c.rejected = :rejected, "
|
||||||
|
+ "c.insufficient = :insufficient, "
|
||||||
|
+ "c.pass = :pass, "
|
||||||
|
+ "c.acceptable = :acceptable, "
|
||||||
|
+ "c.good = :good, "
|
||||||
|
+ "c.veryGood = :veryGood, "
|
||||||
|
+ "c.excellent = :excellent "
|
||||||
|
+"WHERE c.id=:id"
|
||||||
|
)
|
||||||
|
.setParameter("rejected",choice.getRejected())
|
||||||
|
.setParameter("insufficient", choice.getInsufficient())
|
||||||
|
.setParameter("pass",choice.getPass())
|
||||||
|
.setParameter("acceptable",choice.getAcceptable())
|
||||||
|
.setParameter("good", choice.getGood())
|
||||||
|
.setParameter("veryGood",choice.getVeryGood())
|
||||||
|
.setParameter("excellent",choice.getExcellent())
|
||||||
|
.setParameter("id", vmVote.getSubThemeId().getId())
|
||||||
|
.executeUpdate() ;
|
||||||
|
super.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
88
src/main/java/fr/lirmm/aren/ws/rest/VMChoiceRESTFacade.java
Normal file
88
src/main/java/fr/lirmm/aren/ws/rest/VMChoiceRESTFacade.java
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package fr.lirmm.aren.ws.rest;
|
||||||
|
|
||||||
|
import fr.lirmm.aren.model.vm.VMChoice;
|
||||||
|
import fr.lirmm.aren.service.vm.VMChoiceService;
|
||||||
|
import fr.lirmm.aren.service.vm.VMVoteService;
|
||||||
|
|
||||||
|
import javax.annotation.security.PermitAll;
|
||||||
|
import javax.annotation.security.RolesAllowed;
|
||||||
|
import javax.enterprise.context.RequestScoped;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 08/07/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@RequestScoped
|
||||||
|
@Path("vm/choices")
|
||||||
|
public class VMChoiceRESTFacade extends AbstractRESTFacade<VMChoice>{
|
||||||
|
@Inject
|
||||||
|
VMChoiceService choiceService ;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
VMVoteService voteService ;
|
||||||
|
@Override
|
||||||
|
protected VMChoiceService getService() {
|
||||||
|
return choiceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param choice
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
//@RolesAllowed({"ADMIN"})
|
||||||
|
@PermitAll
|
||||||
|
public VMChoice create(VMChoice choice){
|
||||||
|
return super.create(choice) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id of the Entity to update
|
||||||
|
* @param choice
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public VMChoice edit(Long id,VMChoice choice){
|
||||||
|
return super.edit(id,choice) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id of the Entity to remove
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@RolesAllowed({"ADMIN"})
|
||||||
|
public void remove(Long id) {
|
||||||
|
super.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@PermitAll
|
||||||
|
public Set<VMChoice> findAll() {
|
||||||
|
boolean withVotes = this.overview == null;
|
||||||
|
Set<VMChoice> choices= choiceService.findAll(withVotes);
|
||||||
|
return choices ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id of the Entity to fetch
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@PermitAll
|
||||||
|
public VMChoice find(Long id) {
|
||||||
|
boolean withVotes = this.overview == null;
|
||||||
|
return choiceService.find(id,withVotes);
|
||||||
|
}
|
||||||
|
}
|
90
src/main/java/fr/lirmm/aren/ws/rest/VMTeamRESTFacade.java
Normal file
90
src/main/java/fr/lirmm/aren/ws/rest/VMTeamRESTFacade.java
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package fr.lirmm.aren.ws.rest;
|
||||||
|
|
||||||
|
import fr.lirmm.aren.model.Team;
|
||||||
|
import fr.lirmm.aren.model.User;
|
||||||
|
import fr.lirmm.aren.model.vm.VMTeam;
|
||||||
|
import fr.lirmm.aren.service.UserService;
|
||||||
|
import fr.lirmm.aren.service.vm.VMTeamService;
|
||||||
|
|
||||||
|
import javax.annotation.security.PermitAll;
|
||||||
|
import javax.annotation.security.RolesAllowed;
|
||||||
|
import javax.enterprise.context.RequestScoped;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.PUT;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 11/07/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@RequestScoped
|
||||||
|
@Path("vm/teams")
|
||||||
|
public class VMTeamRESTFacade extends AbstractRESTFacade<VMTeam>{
|
||||||
|
@Inject
|
||||||
|
VMTeamService teamService ;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
UserService userService ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected VMTeamService getService(){
|
||||||
|
return teamService ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param team
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@PermitAll
|
||||||
|
public VMTeam create(VMTeam team){
|
||||||
|
SortedSet<User> members=team.getMembers() ;
|
||||||
|
VMTeam teamRes= teamService.insert(team) ;
|
||||||
|
members.forEach(member->{
|
||||||
|
teamRes.addUser(userService.getReference(member.getId()));
|
||||||
|
teamService.edit(teamRes);
|
||||||
|
});
|
||||||
|
return teamRes ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id of the Entity to update
|
||||||
|
* @param team
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public VMTeam edit(Long id, VMTeam team){
|
||||||
|
return super.edit(id,team) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@PermitAll
|
||||||
|
public Set<VMTeam> findAll() {
|
||||||
|
Set<VMTeam> teams= teamService.findAll();
|
||||||
|
return teams ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id of the Entity to fetch
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@PermitAll
|
||||||
|
public VMTeam find(Long id) {
|
||||||
|
return teamService.find(id);
|
||||||
|
}
|
||||||
|
}
|
136
src/main/java/fr/lirmm/aren/ws/rest/VMThemeRESTFacade.java
Normal file
136
src/main/java/fr/lirmm/aren/ws/rest/VMThemeRESTFacade.java
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package fr.lirmm.aren.ws.rest;
|
||||||
|
|
||||||
|
import fr.lirmm.aren.model.vm.VMChoice;
|
||||||
|
import fr.lirmm.aren.model.vm.VMTheme;
|
||||||
|
import fr.lirmm.aren.service.vm.VMChoiceService;
|
||||||
|
import fr.lirmm.aren.service.vm.VMThemeService;
|
||||||
|
import fr.lirmm.aren.service.vm.VMVoteService;
|
||||||
|
import fr.mieuxvoter.mj.*;
|
||||||
|
|
||||||
|
import javax.annotation.security.PermitAll;
|
||||||
|
import javax.annotation.security.RolesAllowed;
|
||||||
|
import javax.enterprise.context.RequestScoped;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.DELETE;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 08/07/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@RequestScoped
|
||||||
|
@Path("vm/themes")
|
||||||
|
public class VMThemeRESTFacade extends AbstractRESTFacade<VMTheme>{
|
||||||
|
@Inject
|
||||||
|
VMThemeService themeService ;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
VMChoiceService choiceService ;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
VMVoteService voteService ;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected VMThemeService getService() {
|
||||||
|
return themeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
//@RolesAllowed({"ADMIN"})
|
||||||
|
@PermitAll
|
||||||
|
public VMTheme create(VMTheme theme){
|
||||||
|
System.out.println(theme.toString());
|
||||||
|
return super.create(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
//@RolesAllowed({"ADMIN"})
|
||||||
|
public VMTheme edit(Long id, VMTheme theme){return super.edit(id, theme);}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PermitAll
|
||||||
|
//@RolesAllowed({"MODO"})
|
||||||
|
public Set<VMTheme> findAll() {
|
||||||
|
boolean withChoices = this.overview == null;
|
||||||
|
Set<VMTheme> themes= themeService.findAll(withChoices, true);
|
||||||
|
/**
|
||||||
|
* Sort by voting rank
|
||||||
|
*/
|
||||||
|
|
||||||
|
Set<VMTheme> newThemes=new HashSet<>() ;
|
||||||
|
|
||||||
|
|
||||||
|
themes.forEach(theme -> {
|
||||||
|
Object []choices=theme.getChoices().toArray() ;
|
||||||
|
List<VMChoice> choicesNotVoted=new ArrayList<>() ;
|
||||||
|
List<ProposalTallyInterface> proposalTallyInterfaces=new ArrayList<>() ;
|
||||||
|
for(int i=0 ; i<choices.length ; i++){
|
||||||
|
VMChoice choice=(VMChoice) choices[i] ;
|
||||||
|
if(choice.isVoted()){
|
||||||
|
proposalTallyInterfaces.add(new ProposalTally(
|
||||||
|
new Integer[]{choice.getRejected(),
|
||||||
|
choice.getInsufficient(),
|
||||||
|
choice.getPass(),
|
||||||
|
choice.getAcceptable(),
|
||||||
|
choice.getGood(),
|
||||||
|
choice.getVeryGood(),
|
||||||
|
choice.getExcellent()})) ;
|
||||||
|
}else{
|
||||||
|
choicesNotVoted.add(choice) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VMChoice newChoices[]=new VMChoice[choices.length] ;
|
||||||
|
if(!proposalTallyInterfaces.isEmpty()){
|
||||||
|
ProposalTallyInterface []proposalTallyInterfacesArray=new ProposalTallyInterface[proposalTallyInterfaces.size()] ;
|
||||||
|
for(int i=0 ; i<proposalTallyInterfaces.size() ; i++){
|
||||||
|
proposalTallyInterfacesArray[i]=proposalTallyInterfaces.get(i) ;
|
||||||
|
}
|
||||||
|
TallyInterface tally = new NormalizedTally(proposalTallyInterfacesArray) ;
|
||||||
|
DeliberatorInterface mj = new MajorityJudgmentDeliberator();
|
||||||
|
ResultInterface result = mj.deliberate(tally);
|
||||||
|
|
||||||
|
|
||||||
|
int index=0 ;
|
||||||
|
for(ProposalResultInterface item : result.getProposalResults()){
|
||||||
|
newChoices[item.getRank()-1]=(VMChoice) choices[index] ;
|
||||||
|
System.out.println(item.getRank()+" - "+newChoices[item.getRank()-1].getTitle());
|
||||||
|
index++ ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0 ; i<choicesNotVoted.size() ; i++){
|
||||||
|
newChoices[newChoices.length-(i+1)]= choicesNotVoted.get(i) ;
|
||||||
|
}
|
||||||
|
LinkedHashSet<VMChoice> setChoices = new LinkedHashSet<>();
|
||||||
|
System.out.println("Rang : ") ;
|
||||||
|
for(int i=0 ; i<newChoices.length ; i++){
|
||||||
|
System.out.println(i+" - "+newChoices[i].getTitle());
|
||||||
|
setChoices.add(newChoices[i]) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
theme.setChoices(setChoices);
|
||||||
|
newThemes.add(theme) ;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return newThemes ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("delete/{id}")
|
||||||
|
@RolesAllowed({"ADMIN"})
|
||||||
|
public void delete(@PathParam("id") Long id) {
|
||||||
|
themeService.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
//@RolesAllowed({"MODO"})
|
||||||
|
@PermitAll
|
||||||
|
public VMTheme find(Long id) {
|
||||||
|
boolean withChoices = this.overview == null;
|
||||||
|
VMTheme theme = themeService.find(id,withChoices,true);
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
}
|
79
src/main/java/fr/lirmm/aren/ws/rest/VMVoteRESTFacade.java
Normal file
79
src/main/java/fr/lirmm/aren/ws/rest/VMVoteRESTFacade.java
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package fr.lirmm.aren.ws.rest;
|
||||||
|
|
||||||
|
|
||||||
|
import fr.lirmm.aren.model.vm.VMChoice;
|
||||||
|
import fr.lirmm.aren.model.vm.VMVote;
|
||||||
|
import fr.lirmm.aren.service.AbstractService;
|
||||||
|
import fr.lirmm.aren.service.vm.VMVoteService;
|
||||||
|
|
||||||
|
import javax.annotation.security.PermitAll;
|
||||||
|
import javax.annotation.security.RolesAllowed;
|
||||||
|
import javax.enterprise.context.RequestScoped;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ANDRIAMBOLAHARIMIHANTA Havana on 08/07/2021
|
||||||
|
* @project aren-1
|
||||||
|
*/
|
||||||
|
@RequestScoped
|
||||||
|
@Path("vm/votes")
|
||||||
|
public class VMVoteRESTFacade extends AbstractRESTFacade<VMVote>{
|
||||||
|
@Inject
|
||||||
|
VMVoteService vmVoteService ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected VMVoteService getService() {
|
||||||
|
return this.vmVoteService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param vote
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
//@RolesAllowed({"USER"})
|
||||||
|
@PermitAll
|
||||||
|
public VMVote create(VMVote vote){
|
||||||
|
return super.create(vote) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param voteId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
//@RolesAllowed({"USER"})
|
||||||
|
@PermitAll
|
||||||
|
public VMVote find(Long voteId){
|
||||||
|
return super.find(voteId) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@RolesAllowed({"ADMIN"})
|
||||||
|
public void remove(Long id) {
|
||||||
|
super.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
//@RolesAllowed({"USER"})
|
||||||
|
@PermitAll
|
||||||
|
public Set<VMVote> findAll(){
|
||||||
|
return super.findAll() ;
|
||||||
|
}
|
||||||
|
}
|
72
src/main/java/fr/mieuxvoter/mj/CollectedTally.java
Normal file
72
src/main/java/fr/mieuxvoter/mj/CollectedTally.java
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
public class CollectedTally implements TallyInterface {
|
||||||
|
|
||||||
|
Integer amountOfProposals = 0;
|
||||||
|
Integer amountOfGrades = 0;
|
||||||
|
|
||||||
|
ProposalTally[] proposalsTallies;
|
||||||
|
|
||||||
|
public CollectedTally(Integer amountOfProposals, Integer amountOfGrades) {
|
||||||
|
setAmountOfProposals(amountOfProposals);
|
||||||
|
setAmountOfGrades(amountOfGrades);
|
||||||
|
|
||||||
|
proposalsTallies = new ProposalTally[amountOfProposals];
|
||||||
|
for (int i = 0; i < amountOfProposals; i++) {
|
||||||
|
ProposalTally proposalTally = new ProposalTally();
|
||||||
|
Integer[] tally = new Integer[amountOfGrades];
|
||||||
|
for (int j = 0; j < amountOfGrades; j++) {
|
||||||
|
tally[j] = 0;
|
||||||
|
}
|
||||||
|
proposalTally.setTally(tally);
|
||||||
|
proposalsTallies[i] = proposalTally;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProposalTallyInterface[] getProposalsTallies() {
|
||||||
|
return proposalsTallies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger getAmountOfJudges() {
|
||||||
|
return guessAmountOfJudges();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getAmountOfProposals() {
|
||||||
|
return this.amountOfProposals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmountOfProposals(Integer amountOfProposals) {
|
||||||
|
this.amountOfProposals = amountOfProposals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getAmountOfGrades() {
|
||||||
|
return amountOfGrades;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmountOfGrades(Integer amountOfGrades) {
|
||||||
|
this.amountOfGrades = amountOfGrades;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BigInteger guessAmountOfJudges() {
|
||||||
|
BigInteger amountOfJudges = BigInteger.ZERO;
|
||||||
|
for (ProposalTallyInterface proposalTally : getProposalsTallies()) {
|
||||||
|
amountOfJudges = proposalTally.getAmountOfJudgments().max(amountOfJudges);
|
||||||
|
}
|
||||||
|
return amountOfJudges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void collect(Integer proposal, Integer grade) {
|
||||||
|
assert (0 <= proposal);
|
||||||
|
assert (amountOfProposals > proposal);
|
||||||
|
assert (0 <= grade);
|
||||||
|
assert (amountOfGrades > grade);
|
||||||
|
|
||||||
|
BigInteger[] tally = proposalsTallies[proposal].getTally();
|
||||||
|
tally[grade] = tally[grade].add(BigInteger.ONE);
|
||||||
|
}
|
||||||
|
}
|
47
src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java
Normal file
47
src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the missing judgments into the grade defined by `getDefaultGrade()`. This is an abstract
|
||||||
|
* class to dry code between static default grade and median default grade.
|
||||||
|
*/
|
||||||
|
public abstract class DefaultGradeTally extends Tally implements TallyInterface {
|
||||||
|
|
||||||
|
/** Override this to choose the default grade for a given proposal. */
|
||||||
|
protected abstract Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally);
|
||||||
|
|
||||||
|
// <domi41> /me is confused with why we need constructors in an abstract class?
|
||||||
|
|
||||||
|
public DefaultGradeTally(TallyInterface tally) {
|
||||||
|
super(tally.getProposalsTallies(), tally.getAmountOfJudges());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) {
|
||||||
|
super(proposalsTallies, amountOfJudges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) {
|
||||||
|
super(proposalsTallies, amountOfJudges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) {
|
||||||
|
super(proposalsTallies, amountOfJudges);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void fillWithDefaultGrade() {
|
||||||
|
int amountOfProposals = getAmountOfProposals();
|
||||||
|
for (int i = 0; i < amountOfProposals; i++) {
|
||||||
|
ProposalTallyInterface proposalTally = getProposalsTallies()[i];
|
||||||
|
Integer defaultGrade = getDefaultGradeForProposal(proposalTally);
|
||||||
|
BigInteger amountOfJudgments = proposalTally.getAmountOfJudgments();
|
||||||
|
BigInteger missingAmount = this.amountOfJudges.subtract(amountOfJudgments);
|
||||||
|
int missingSign = missingAmount.compareTo(BigInteger.ZERO);
|
||||||
|
assert (0 <= missingSign); // ERROR: More judgments than judges!
|
||||||
|
if (0 < missingSign) {
|
||||||
|
BigInteger[] rawTally = proposalTally.getTally();
|
||||||
|
rawTally[defaultGrade] = rawTally[defaultGrade].add(missingAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java
Normal file
18
src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Deliberator takes in a poll's Tally, which holds the amount of judgments of each grade received
|
||||||
|
* by each Proposal, and outputs that poll's Result, that is the final rank of each Proposal.
|
||||||
|
*
|
||||||
|
* <p>Ranks start at 1 ("best"), and increment towards "worst". Two proposal may share the same
|
||||||
|
* rank, in extreme equality cases.
|
||||||
|
*
|
||||||
|
* <p>This is the main API of this library.
|
||||||
|
*
|
||||||
|
* <p>See MajorityJudgmentDeliberator for an implementation. One could implement other deliberators,
|
||||||
|
* such as: - CentralJudgmentDeliberator - UsualJudgmentDeliberator
|
||||||
|
*/
|
||||||
|
public interface DeliberatorInterface {
|
||||||
|
|
||||||
|
public ResultInterface deliberate(TallyInterface tally) throws InvalidTallyException;
|
||||||
|
}
|
13
src/main/java/fr/mieuxvoter/mj/IncoherentTallyException.java
Normal file
13
src/main/java/fr/mieuxvoter/mj/IncoherentTallyException.java
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
/** Raised when the provided tally holds negative values, or infinity. */
|
||||||
|
class IncoherentTallyException extends InvalidTallyException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 5858986651601202903L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return ("The provided tally holds negative values, or infinity. "
|
||||||
|
+ (null == super.getMessage() ? "" : super.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
|
|
||||||
|
/** Raised when the provided tally is invalid. */
|
||||||
|
class InvalidTallyException extends InvalidParameterException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3033391835216704620L;
|
||||||
|
}
|
197
src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java
Normal file
197
src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deliberate using Majority Judgment.
|
||||||
|
*
|
||||||
|
* <p>Sorts Proposals by their median Grade. When two proposals share the same median Grade, give
|
||||||
|
* reason to the largest group of people that did not give the median Grade.
|
||||||
|
*
|
||||||
|
* <p>This algorithm is score-based, for performance (and possible parallelization). Each Proposal
|
||||||
|
* gets a score, higher (lexicographically) is "better" (depends of the meaning of the Grades). We
|
||||||
|
* use Strings instead of Integers or raw Bits for the score. Improve if you feel like it and can
|
||||||
|
* benchmark things.
|
||||||
|
*
|
||||||
|
* <p>https://en.wikipedia.org/wiki/Majority_judgment
|
||||||
|
* https://fr.wikipedia.org/wiki/Jugement_majoritaire
|
||||||
|
*
|
||||||
|
* <p>Should this class be `final`?
|
||||||
|
*/
|
||||||
|
public final class MajorityJudgmentDeliberator implements DeliberatorInterface {
|
||||||
|
|
||||||
|
protected boolean favorContestation = true;
|
||||||
|
protected boolean numerizeScore = false;
|
||||||
|
|
||||||
|
public MajorityJudgmentDeliberator() {}
|
||||||
|
|
||||||
|
public MajorityJudgmentDeliberator(boolean favorContestation) {
|
||||||
|
this.favorContestation = favorContestation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MajorityJudgmentDeliberator(boolean favorContestation, boolean numerizeScore) {
|
||||||
|
this.favorContestation = favorContestation;
|
||||||
|
this.numerizeScore = numerizeScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultInterface deliberate(TallyInterface tally) throws InvalidTallyException {
|
||||||
|
checkTally(tally);
|
||||||
|
|
||||||
|
ProposalTallyInterface[] tallies = tally.getProposalsTallies();
|
||||||
|
BigInteger amountOfJudges = tally.getAmountOfJudges();
|
||||||
|
Integer amountOfProposals = tally.getAmountOfProposals();
|
||||||
|
|
||||||
|
Result result = new Result();
|
||||||
|
ProposalResult[] proposalResults = new ProposalResult[amountOfProposals];
|
||||||
|
|
||||||
|
// I. Compute the scores of each Proposal
|
||||||
|
for (int proposalIndex = 0; proposalIndex < amountOfProposals; proposalIndex++) {
|
||||||
|
ProposalTallyInterface proposalTally = tallies[proposalIndex];
|
||||||
|
String score = computeScore(proposalTally, amountOfJudges);
|
||||||
|
ProposalTallyAnalysis analysis =
|
||||||
|
new ProposalTallyAnalysis(proposalTally, this.favorContestation);
|
||||||
|
ProposalResult proposalResult = new ProposalResult();
|
||||||
|
proposalResult.setScore(score);
|
||||||
|
proposalResult.setAnalysis(analysis);
|
||||||
|
// proposalResult.setRank(???); // rank is computed below, AFTER the score pass
|
||||||
|
proposalResults[proposalIndex] = proposalResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// II. Sort Proposals by score (lexicographical inverse)
|
||||||
|
ProposalResult[] proposalResultsSorted = proposalResults.clone();
|
||||||
|
assert (proposalResultsSorted[0].hashCode()
|
||||||
|
== proposalResults[0].hashCode()); // we need a shallow clone
|
||||||
|
Arrays.sort(
|
||||||
|
proposalResultsSorted,
|
||||||
|
(Comparator<ProposalResultInterface>) (p0, p1) -> p1.getScore().compareTo(p0.getScore()));
|
||||||
|
|
||||||
|
// III. Attribute a rank to each Proposal
|
||||||
|
Integer rank = 1;
|
||||||
|
for (int proposalIndex = 0; proposalIndex < amountOfProposals; proposalIndex++) {
|
||||||
|
ProposalResult proposalResult = proposalResultsSorted[proposalIndex];
|
||||||
|
Integer actualRank = rank;
|
||||||
|
if (proposalIndex > 0) {
|
||||||
|
ProposalResult proposalResultBefore = proposalResultsSorted[proposalIndex - 1];
|
||||||
|
if (proposalResult.getScore().contentEquals(proposalResultBefore.getScore())) {
|
||||||
|
actualRank = proposalResultBefore.getRank();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
proposalResult.setRank(actualRank);
|
||||||
|
rank += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.setProposalResults(proposalResults);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkTally(TallyInterface tally) throws UnbalancedTallyException {
|
||||||
|
if (!isTallyCoherent(tally)) {
|
||||||
|
throw new IncoherentTallyException();
|
||||||
|
}
|
||||||
|
if (!isTallyBalanced(tally)) {
|
||||||
|
throw new UnbalancedTallyException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isTallyCoherent(TallyInterface tally) {
|
||||||
|
boolean coherent = true;
|
||||||
|
for (ProposalTallyInterface proposalTally : tally.getProposalsTallies()) {
|
||||||
|
for (BigInteger gradeTally : proposalTally.getTally()) {
|
||||||
|
if (-1 == gradeTally.compareTo(BigInteger.ZERO)) {
|
||||||
|
coherent = false; // negative tallies are not coherent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return coherent;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isTallyBalanced(TallyInterface tally) {
|
||||||
|
boolean balanced = true;
|
||||||
|
BigInteger amountOfJudges = BigInteger.ZERO;
|
||||||
|
boolean firstProposal = true;
|
||||||
|
for (ProposalTallyInterface proposalTally : tally.getProposalsTallies()) {
|
||||||
|
if (firstProposal) {
|
||||||
|
amountOfJudges = proposalTally.getAmountOfJudgments();
|
||||||
|
firstProposal = false;
|
||||||
|
} else {
|
||||||
|
if (0 != amountOfJudges.compareTo(proposalTally.getAmountOfJudgments())) {
|
||||||
|
balanced = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return balanced;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @see computeScore() below */
|
||||||
|
protected String computeScore(ProposalTallyInterface tally, BigInteger amountOfJudges) {
|
||||||
|
return computeScore(tally, amountOfJudges, this.favorContestation, this.numerizeScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A higher score means a better rank. Assumes that grades' tallies are provided from "worst"
|
||||||
|
* grade to "best" grade.
|
||||||
|
*
|
||||||
|
* @param tally Holds the tallies of each Grade for a single Proposal
|
||||||
|
* @param amountOfJudges
|
||||||
|
* @param favorContestation Use the lower median, for example
|
||||||
|
* @param onlyNumbers Do not use separation characters, match `^[0-9]+$`
|
||||||
|
* @return the score of the proposal
|
||||||
|
*/
|
||||||
|
protected String computeScore(
|
||||||
|
ProposalTallyInterface tally,
|
||||||
|
BigInteger amountOfJudges,
|
||||||
|
Boolean favorContestation,
|
||||||
|
Boolean onlyNumbers) {
|
||||||
|
ProposalTallyAnalysis analysis = new ProposalTallyAnalysis();
|
||||||
|
int amountOfGrades = tally.getTally().length;
|
||||||
|
int digitsForGrade = countDigits(amountOfGrades);
|
||||||
|
int digitsForGroup = countDigits(amountOfJudges) + 1;
|
||||||
|
|
||||||
|
ProposalTallyInterface currentTally = tally.duplicate();
|
||||||
|
|
||||||
|
String score = "";
|
||||||
|
for (int i = 0; i < amountOfGrades; i++) {
|
||||||
|
|
||||||
|
analysis.reanalyze(currentTally, favorContestation);
|
||||||
|
|
||||||
|
if (0 < i && !onlyNumbers) {
|
||||||
|
score += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
score += String.format("%0" + digitsForGrade + "d", analysis.getMedianGrade());
|
||||||
|
|
||||||
|
if (!onlyNumbers) {
|
||||||
|
score += "_";
|
||||||
|
}
|
||||||
|
|
||||||
|
score +=
|
||||||
|
String.format(
|
||||||
|
"%0" + digitsForGroup + "d",
|
||||||
|
// We offset by amountOfJudges to keep a lexicographical order (no
|
||||||
|
// negatives)
|
||||||
|
// amountOfJudges + secondMedianGroupSize * secondMedianGroupSign
|
||||||
|
amountOfJudges.add(
|
||||||
|
analysis.getSecondMedianGroupSize()
|
||||||
|
.multiply(
|
||||||
|
BigInteger.valueOf(
|
||||||
|
analysis.getSecondMedianGroupSign()))));
|
||||||
|
|
||||||
|
currentTally.moveJudgments(analysis.getMedianGrade(), analysis.getSecondMedianGrade());
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int countDigits(int number) {
|
||||||
|
return ("" + number).length();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int countDigits(BigInteger number) {
|
||||||
|
return ("" + number).length();
|
||||||
|
}
|
||||||
|
}
|
37
src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java
Normal file
37
src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the missing judgments into the median grade of each proposal. Useful when the proposals have
|
||||||
|
* not received the exact same amount of votes and the median grade is considered a sane default.
|
||||||
|
*/
|
||||||
|
public class MedianDefaultTally extends DefaultGradeTally implements TallyInterface {
|
||||||
|
|
||||||
|
public MedianDefaultTally(TallyInterface tally) {
|
||||||
|
super(tally.getProposalsTallies(), tally.getAmountOfJudges());
|
||||||
|
fillWithDefaultGrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MedianDefaultTally(
|
||||||
|
ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) {
|
||||||
|
super(proposalsTallies, amountOfJudges);
|
||||||
|
fillWithDefaultGrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MedianDefaultTally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) {
|
||||||
|
super(proposalsTallies, amountOfJudges);
|
||||||
|
fillWithDefaultGrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MedianDefaultTally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) {
|
||||||
|
super(proposalsTallies, amountOfJudges);
|
||||||
|
fillWithDefaultGrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally) {
|
||||||
|
ProposalTallyAnalysis analysis = new ProposalTallyAnalysis(proposalTally);
|
||||||
|
return analysis.getMedianGrade();
|
||||||
|
}
|
||||||
|
}
|
75
src/main/java/fr/mieuxvoter/mj/NormalizedTally.java
Normal file
75
src/main/java/fr/mieuxvoter/mj/NormalizedTally.java
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The deliberator expects the proposals' tallies to hold the same amount of judgments. This
|
||||||
|
* NormalizedTally accepts tallies with disparate amounts of judgments per proposal, and normalizes
|
||||||
|
* them to their least common multiple, which amounts to using percentages, except we don't use
|
||||||
|
* floating-point arithmetic.
|
||||||
|
*
|
||||||
|
* <p>This is useful when there are too many proposals for judges to be expected to judge them all,
|
||||||
|
* and all the proposals received reasonably similar amounts of judgments.
|
||||||
|
*/
|
||||||
|
public class NormalizedTally extends Tally implements TallyInterface {
|
||||||
|
|
||||||
|
public NormalizedTally(ProposalTallyInterface[] proposalsTallies) {
|
||||||
|
super(proposalsTallies);
|
||||||
|
initializeFromProposalsTallies(proposalsTallies);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NormalizedTally(TallyInterface tally) {
|
||||||
|
super(tally.getProposalsTallies());
|
||||||
|
initializeFromProposalsTallies(tally.getProposalsTallies());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initializeFromProposalsTallies(ProposalTallyInterface[] proposalsTallies) {
|
||||||
|
Integer amountOfProposals = getAmountOfProposals();
|
||||||
|
|
||||||
|
// Compute the Least Common Multiple
|
||||||
|
BigInteger amountOfJudges = BigInteger.ONE;
|
||||||
|
for (ProposalTallyInterface proposalTally : proposalsTallies) {
|
||||||
|
amountOfJudges = lcm(amountOfJudges, proposalTally.getAmountOfJudgments());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == amountOfJudges.compareTo(BigInteger.ZERO)) {
|
||||||
|
throw new InvalidParameterException(
|
||||||
|
"Cannot normalize: one or more proposals have no judgments.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize proposals to the LCM
|
||||||
|
ProposalTally[] normalizedTallies = new ProposalTally[amountOfProposals];
|
||||||
|
for (int i = 0; i < amountOfProposals; i++) {
|
||||||
|
ProposalTallyInterface proposalTally = proposalsTallies[i];
|
||||||
|
ProposalTally normalizedTally = new ProposalTally(proposalTally);
|
||||||
|
BigInteger factor = amountOfJudges.divide(proposalTally.getAmountOfJudgments());
|
||||||
|
Integer amountOfGrades = proposalTally.getTally().length;
|
||||||
|
BigInteger[] gradesTallies = normalizedTally.getTally();
|
||||||
|
for (int j = 0; j < amountOfGrades; j++) {
|
||||||
|
gradesTallies[j] = gradesTallies[j].multiply(factor);
|
||||||
|
}
|
||||||
|
normalizedTallies[i] = normalizedTally;
|
||||||
|
}
|
||||||
|
|
||||||
|
setProposalsTallies(normalizedTallies);
|
||||||
|
setAmountOfJudges(amountOfJudges);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Least Common Multiple
|
||||||
|
*
|
||||||
|
* <p>http://en.wikipedia.org/wiki/Least_common_multiple
|
||||||
|
*
|
||||||
|
* <p>lcm( 6, 9 ) = 18 lcm( 4, 9 ) = 36 lcm( 0, 9 ) = 0 lcm( 0, 0 ) = 0
|
||||||
|
*
|
||||||
|
* @author www.java2s.com
|
||||||
|
* @param a first integer
|
||||||
|
* @param b second integer
|
||||||
|
* @return least common multiple of a and b
|
||||||
|
*/
|
||||||
|
public static BigInteger lcm(BigInteger a, BigInteger b) {
|
||||||
|
if (a.signum() == 0 || b.signum() == 0) return BigInteger.ZERO;
|
||||||
|
return a.divide(a.gcd(b)).multiply(b).abs();
|
||||||
|
}
|
||||||
|
}
|
34
src/main/java/fr/mieuxvoter/mj/ProposalResult.java
Normal file
34
src/main/java/fr/mieuxvoter/mj/ProposalResult.java
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
public class ProposalResult implements ProposalResultInterface {
|
||||||
|
|
||||||
|
protected Integer rank;
|
||||||
|
|
||||||
|
protected String score;
|
||||||
|
|
||||||
|
protected ProposalTallyAnalysis analysis;
|
||||||
|
|
||||||
|
public Integer getRank() {
|
||||||
|
return rank;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRank(Integer rank) {
|
||||||
|
this.rank = rank;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScore() {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScore(String score) {
|
||||||
|
this.score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProposalTallyAnalysis getAnalysis() {
|
||||||
|
return analysis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnalysis(ProposalTallyAnalysis analysis) {
|
||||||
|
this.analysis = analysis;
|
||||||
|
}
|
||||||
|
}
|
21
src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java
Normal file
21
src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
public interface ProposalResultInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rank starts at 1 ("best" proposal), and goes upwards. Multiple Proposals may receive the same
|
||||||
|
* rank, in the extreme case where they received the exact same judgments, or the same judgment
|
||||||
|
* repartition in normalized tallies.
|
||||||
|
*/
|
||||||
|
public Integer getRank();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This score was used to compute the rank. It is made of integer characters, with zeroes for
|
||||||
|
* padding. Inverse lexicographical order: "higher" is "better". You're probably never going to
|
||||||
|
* need this, but it's here anyway.
|
||||||
|
*/
|
||||||
|
public String getScore();
|
||||||
|
|
||||||
|
/** Get more data about the proposal tally, such as the median grade. */
|
||||||
|
public ProposalTallyAnalysis getAnalysis();
|
||||||
|
}
|
92
src/main/java/fr/mieuxvoter/mj/ProposalTally.java
Normal file
92
src/main/java/fr/mieuxvoter/mj/ProposalTally.java
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class ProposalTally implements ProposalTallyInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amounts of judgments received per grade, from "worst" grade to "best" grade. Those are
|
||||||
|
* BigIntegers because of our LCM-based normalization shenanigans.
|
||||||
|
*/
|
||||||
|
protected BigInteger[] tally;
|
||||||
|
|
||||||
|
public ProposalTally() {}
|
||||||
|
|
||||||
|
public ProposalTally(String[] tally) {
|
||||||
|
setTally(tally);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProposalTally(Integer[] tally) {
|
||||||
|
setTally(tally);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProposalTally(Long[] tally) {
|
||||||
|
setTally(tally);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProposalTally(BigInteger[] tally) {
|
||||||
|
setTally(tally);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProposalTally(ProposalTallyInterface proposalTally) {
|
||||||
|
setTally(Arrays.copyOf(proposalTally.getTally(), proposalTally.getTally().length));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTally(String[] tally) {
|
||||||
|
int tallyLength = tally.length;
|
||||||
|
BigInteger[] bigTally = new BigInteger[tallyLength];
|
||||||
|
for (int i = 0; i < tallyLength; i++) {
|
||||||
|
bigTally[i] = new BigInteger(tally[i]);
|
||||||
|
}
|
||||||
|
setTally(bigTally);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTally(Integer[] tally) {
|
||||||
|
int tallyLength = tally.length;
|
||||||
|
BigInteger[] bigTally = new BigInteger[tallyLength];
|
||||||
|
for (int i = 0; i < tallyLength; i++) {
|
||||||
|
bigTally[i] = BigInteger.valueOf(tally[i]);
|
||||||
|
}
|
||||||
|
setTally(bigTally);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTally(Long[] tally) {
|
||||||
|
int tallyLength = tally.length;
|
||||||
|
BigInteger[] bigTally = new BigInteger[tallyLength];
|
||||||
|
for (int i = 0; i < tallyLength; i++) {
|
||||||
|
bigTally[i] = BigInteger.valueOf(tally[i]);
|
||||||
|
}
|
||||||
|
setTally(bigTally);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTally(BigInteger[] tally) {
|
||||||
|
this.tally = tally;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger[] getTally() {
|
||||||
|
return this.tally;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProposalTallyInterface duplicate() {
|
||||||
|
return new ProposalTally(Arrays.copyOf(this.tally, this.tally.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveJudgments(Integer fromGrade, Integer intoGrade) {
|
||||||
|
this.tally[intoGrade] = this.tally[intoGrade].add(this.tally[fromGrade]);
|
||||||
|
this.tally[fromGrade] = BigInteger.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger getAmountOfJudgments() {
|
||||||
|
BigInteger sum = BigInteger.ZERO;
|
||||||
|
int tallyLength = this.tally.length;
|
||||||
|
for (int i = 0; i < tallyLength; i++) {
|
||||||
|
sum = sum.add(this.tally[i]);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
}
|
179
src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java
Normal file
179
src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect useful data on a proposal tally. Does NOT compute the rank, but provides all we need.
|
||||||
|
*
|
||||||
|
* <p>This uses BigInteger because in a normalization scenario we use the smallest common multiple
|
||||||
|
* of the amounts of judges of proposals. It makes the code harder to read and understand, but it
|
||||||
|
* allows us to bypass the floating-point nightmare of the normalization of merit profiles, which is
|
||||||
|
* one way to handle default grades on some polls.
|
||||||
|
*/
|
||||||
|
public class ProposalTallyAnalysis {
|
||||||
|
|
||||||
|
protected ProposalTallyInterface tally;
|
||||||
|
|
||||||
|
protected BigInteger totalSize = BigInteger.ZERO; // amount of judges
|
||||||
|
|
||||||
|
protected Integer medianGrade = 0;
|
||||||
|
|
||||||
|
protected BigInteger medianGroupSize = BigInteger.ZERO; // amount of judges in the median group
|
||||||
|
|
||||||
|
protected Integer contestationGrade = 0; // "best" grade of the contestation group
|
||||||
|
|
||||||
|
protected BigInteger contestationGroupSize = BigInteger.ZERO; // of lower grades than median
|
||||||
|
|
||||||
|
protected Integer adhesionGrade = 0; // "worst" grade of the adhesion group
|
||||||
|
|
||||||
|
protected BigInteger adhesionGroupSize = BigInteger.ZERO; // of higher grades than median
|
||||||
|
|
||||||
|
protected Integer secondMedianGrade = 0; // grade of the biggest group out of the median
|
||||||
|
|
||||||
|
protected BigInteger secondMedianGroupSize = BigInteger.ZERO; // either contestation or adhesion
|
||||||
|
|
||||||
|
protected Integer secondMedianGroupSign =
|
||||||
|
0; // -1 for contestation, +1 for adhesion, 0 for empty group size
|
||||||
|
|
||||||
|
public ProposalTallyAnalysis() {}
|
||||||
|
|
||||||
|
public ProposalTallyAnalysis(ProposalTallyInterface tally) {
|
||||||
|
reanalyze(tally);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProposalTallyAnalysis(ProposalTallyInterface tally, Boolean favorContestation) {
|
||||||
|
reanalyze(tally, favorContestation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reanalyze(ProposalTallyInterface tally) {
|
||||||
|
reanalyze(tally, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reanalyze(ProposalTallyInterface tally, Boolean favorContestation) {
|
||||||
|
this.tally = tally;
|
||||||
|
this.totalSize = BigInteger.ZERO;
|
||||||
|
this.medianGrade = 0;
|
||||||
|
this.medianGroupSize = BigInteger.ZERO;
|
||||||
|
this.contestationGrade = 0;
|
||||||
|
this.contestationGroupSize = BigInteger.ZERO;
|
||||||
|
this.adhesionGrade = 0;
|
||||||
|
this.adhesionGroupSize = BigInteger.ZERO;
|
||||||
|
this.secondMedianGrade = 0;
|
||||||
|
this.secondMedianGroupSize = BigInteger.ZERO;
|
||||||
|
this.secondMedianGroupSign = 0;
|
||||||
|
|
||||||
|
BigInteger[] gradesTallies = this.tally.getTally();
|
||||||
|
int amountOfGrades = gradesTallies.length;
|
||||||
|
|
||||||
|
for (int grade = 0; grade < amountOfGrades; grade++) {
|
||||||
|
BigInteger gradeTally = gradesTallies[grade];
|
||||||
|
// assert(0 <= gradeTally); // Negative tallies are not allowed.
|
||||||
|
this.totalSize = this.totalSize.add(gradeTally);
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer medianOffset = 1;
|
||||||
|
if (!favorContestation) {
|
||||||
|
medianOffset = 2;
|
||||||
|
}
|
||||||
|
BigInteger medianCursor =
|
||||||
|
this.totalSize.add(BigInteger.valueOf(medianOffset)).divide(BigInteger.valueOf(2));
|
||||||
|
// Long medianCursor = (long) Math.floor((this.totalSize + medianOffset) / 2.0);
|
||||||
|
|
||||||
|
BigInteger tallyBeforeCursor = BigInteger.ZERO;
|
||||||
|
BigInteger tallyCursor = BigInteger.ZERO;
|
||||||
|
Boolean foundMedian = false;
|
||||||
|
Integer contestationGrade = 0;
|
||||||
|
Integer adhesionGrade = 0;
|
||||||
|
for (int grade = 0; grade < amountOfGrades; grade++) {
|
||||||
|
BigInteger gradeTally = gradesTallies[grade];
|
||||||
|
tallyBeforeCursor = tallyCursor;
|
||||||
|
tallyCursor = tallyCursor.add(gradeTally);
|
||||||
|
|
||||||
|
if (!foundMedian) {
|
||||||
|
if (-1 < tallyCursor.compareTo(medianCursor)) { // tallyCursor >= medianCursor
|
||||||
|
foundMedian = true;
|
||||||
|
this.medianGrade = grade;
|
||||||
|
this.contestationGroupSize = tallyBeforeCursor;
|
||||||
|
this.medianGroupSize = gradeTally;
|
||||||
|
this.adhesionGroupSize =
|
||||||
|
this.totalSize
|
||||||
|
.subtract(this.contestationGroupSize)
|
||||||
|
.subtract(this.medianGroupSize);
|
||||||
|
} else {
|
||||||
|
if (1 == gradeTally.compareTo(BigInteger.ZERO)) { // 0 < gradeTally
|
||||||
|
contestationGrade = grade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (1 == gradeTally.compareTo(BigInteger.ZERO) && 0 == adhesionGrade) {
|
||||||
|
adhesionGrade = grade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.contestationGrade = contestationGrade;
|
||||||
|
this.adhesionGrade = adhesionGrade;
|
||||||
|
this.secondMedianGroupSize = this.contestationGroupSize.max(this.adhesionGroupSize);
|
||||||
|
this.secondMedianGroupSign = 0;
|
||||||
|
// if (this.contestationGroupSize < this.adhesionGroupSize) {
|
||||||
|
if (1 == this.adhesionGroupSize.compareTo(this.contestationGroupSize)) {
|
||||||
|
this.secondMedianGrade = this.adhesionGrade;
|
||||||
|
this.secondMedianGroupSign = 1;
|
||||||
|
// } else if (this.contestationGroupSize > this.adhesionGroupSize) {
|
||||||
|
} else if (1 == this.contestationGroupSize.compareTo(this.adhesionGroupSize)) {
|
||||||
|
this.secondMedianGrade = this.contestationGrade;
|
||||||
|
this.secondMedianGroupSign = -1;
|
||||||
|
} else {
|
||||||
|
if (favorContestation) {
|
||||||
|
this.secondMedianGrade = this.contestationGrade;
|
||||||
|
this.secondMedianGroupSign = -1;
|
||||||
|
} else {
|
||||||
|
this.secondMedianGrade = this.adhesionGrade;
|
||||||
|
this.secondMedianGroupSign = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (0 == this.secondMedianGroupSize.compareTo(BigInteger.ZERO)) {
|
||||||
|
this.secondMedianGroupSign = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getTotalSize() {
|
||||||
|
return totalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getMedianGrade() {
|
||||||
|
return medianGrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getMedianGroupSize() {
|
||||||
|
return medianGroupSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getContestationGrade() {
|
||||||
|
return contestationGrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getContestationGroupSize() {
|
||||||
|
return contestationGroupSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getAdhesionGrade() {
|
||||||
|
return adhesionGrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getAdhesionGroupSize() {
|
||||||
|
return adhesionGroupSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSecondMedianGrade() {
|
||||||
|
return secondMedianGrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getSecondMedianGroupSize() {
|
||||||
|
return secondMedianGroupSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSecondMedianGroupSign() {
|
||||||
|
return secondMedianGroupSign;
|
||||||
|
}
|
||||||
|
}
|
29
src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java
Normal file
29
src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Also known as the merit profile of a proposal (aka. candidate), this holds the amounts of
|
||||||
|
* judgments received per grade.
|
||||||
|
*/
|
||||||
|
public interface ProposalTallyInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tallies of each Grade, that is the amount of judgments received for each Grade by the
|
||||||
|
* Proposal, from "worst" ("most conservative") Grade to "best" Grade.
|
||||||
|
*/
|
||||||
|
public BigInteger[] getTally();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be the sum of getTally()
|
||||||
|
*
|
||||||
|
* @return The total amount of judgments received by this proposal.
|
||||||
|
*/
|
||||||
|
public BigInteger getAmountOfJudgments();
|
||||||
|
|
||||||
|
/** Homemade factory to skip the clone() shenanigans. Used by the score calculus. */
|
||||||
|
public ProposalTallyInterface duplicate();
|
||||||
|
|
||||||
|
/** Move judgments that were fromGrade into intoGrade. Used by the score calculus. */
|
||||||
|
public void moveJudgments(Integer fromGrade, Integer intoGrade);
|
||||||
|
}
|
14
src/main/java/fr/mieuxvoter/mj/Result.java
Normal file
14
src/main/java/fr/mieuxvoter/mj/Result.java
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
public class Result implements ResultInterface {
|
||||||
|
|
||||||
|
protected ProposalResultInterface[] proposalResults;
|
||||||
|
|
||||||
|
public ProposalResultInterface[] getProposalResults() {
|
||||||
|
return proposalResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProposalResults(ProposalResultInterface[] proposalResults) {
|
||||||
|
this.proposalResults = proposalResults;
|
||||||
|
}
|
||||||
|
}
|
12
src/main/java/fr/mieuxvoter/mj/ResultInterface.java
Normal file
12
src/main/java/fr/mieuxvoter/mj/ResultInterface.java
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
public interface ResultInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProposalResults are not ordered by rank, they are in the order the proposals' tallies were
|
||||||
|
* submitted.
|
||||||
|
*
|
||||||
|
* @return an array of `ProposalResult`, in the order the `ProposalTally`s were submitted.
|
||||||
|
*/
|
||||||
|
public ProposalResultInterface[] getProposalResults();
|
||||||
|
}
|
53
src/main/java/fr/mieuxvoter/mj/StaticDefaultTally.java
Normal file
53
src/main/java/fr/mieuxvoter/mj/StaticDefaultTally.java
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
public class StaticDefaultTally extends DefaultGradeTally implements TallyInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grades are represented as numbers, as indices in a list. Grades start from 0 ("worst" grade,
|
||||||
|
* most conservative) and go upwards. Values out of the range of grades defined in the tally
|
||||||
|
* will yield errors.
|
||||||
|
*
|
||||||
|
* <p>Example:
|
||||||
|
*
|
||||||
|
* <p>0 == REJECT 1 == PASSABLE 2 == GOOD 3 == EXCELLENT
|
||||||
|
*/
|
||||||
|
protected Integer defaultGrade = 0;
|
||||||
|
|
||||||
|
public StaticDefaultTally(TallyInterface tally, Integer defaultGrade) {
|
||||||
|
super(tally.getProposalsTallies(), tally.getAmountOfJudges());
|
||||||
|
this.defaultGrade = defaultGrade;
|
||||||
|
fillWithDefaultGrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StaticDefaultTally(
|
||||||
|
ProposalTallyInterface[] proposalsTallies,
|
||||||
|
BigInteger amountOfJudges,
|
||||||
|
Integer defaultGrade) {
|
||||||
|
super(proposalsTallies, amountOfJudges);
|
||||||
|
this.defaultGrade = defaultGrade;
|
||||||
|
fillWithDefaultGrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StaticDefaultTally(
|
||||||
|
ProposalTallyInterface[] proposalsTallies, Long amountOfJudges, Integer defaultGrade) {
|
||||||
|
super(proposalsTallies, amountOfJudges);
|
||||||
|
this.defaultGrade = defaultGrade;
|
||||||
|
fillWithDefaultGrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StaticDefaultTally(
|
||||||
|
ProposalTallyInterface[] proposalsTallies,
|
||||||
|
Integer amountOfJudges,
|
||||||
|
Integer defaultGrade) {
|
||||||
|
super(proposalsTallies, amountOfJudges);
|
||||||
|
this.defaultGrade = defaultGrade;
|
||||||
|
fillWithDefaultGrade();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally) {
|
||||||
|
return this.defaultGrade;
|
||||||
|
}
|
||||||
|
}
|
61
src/main/java/fr/mieuxvoter/mj/Tally.java
Normal file
61
src/main/java/fr/mieuxvoter/mj/Tally.java
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Basic implementation of a TallyInterface that reads from an array of ProposalTallyInterface.
|
||||||
|
*/
|
||||||
|
public class Tally implements TallyInterface {
|
||||||
|
|
||||||
|
protected ProposalTallyInterface[] proposalsTallies;
|
||||||
|
|
||||||
|
protected BigInteger amountOfJudges = BigInteger.ZERO;
|
||||||
|
|
||||||
|
public Tally(ProposalTallyInterface[] proposalsTallies) {
|
||||||
|
setProposalsTallies(proposalsTallies);
|
||||||
|
guessAmountOfJudges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tally(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) {
|
||||||
|
setProposalsTallies(proposalsTallies);
|
||||||
|
setAmountOfJudges(amountOfJudges);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) {
|
||||||
|
setProposalsTallies(proposalsTallies);
|
||||||
|
setAmountOfJudges(BigInteger.valueOf(amountOfJudges));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) {
|
||||||
|
setProposalsTallies(proposalsTallies);
|
||||||
|
setAmountOfJudges(BigInteger.valueOf(amountOfJudges));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProposalTallyInterface[] getProposalsTallies() {
|
||||||
|
return proposalsTallies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProposalsTallies(ProposalTallyInterface[] proposalsTallies) {
|
||||||
|
this.proposalsTallies = proposalsTallies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getAmountOfProposals() {
|
||||||
|
return proposalsTallies.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getAmountOfJudges() {
|
||||||
|
return amountOfJudges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmountOfJudges(BigInteger amountOfJudges) {
|
||||||
|
this.amountOfJudges = amountOfJudges;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void guessAmountOfJudges() {
|
||||||
|
BigInteger amountOfJudges = BigInteger.ZERO;
|
||||||
|
for (ProposalTallyInterface proposalTally : getProposalsTallies()) {
|
||||||
|
amountOfJudges = proposalTally.getAmountOfJudgments().max(amountOfJudges);
|
||||||
|
}
|
||||||
|
setAmountOfJudges(amountOfJudges);
|
||||||
|
}
|
||||||
|
}
|
12
src/main/java/fr/mieuxvoter/mj/TallyInterface.java
Normal file
12
src/main/java/fr/mieuxvoter/mj/TallyInterface.java
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
public interface TallyInterface {
|
||||||
|
|
||||||
|
public ProposalTallyInterface[] getProposalsTallies();
|
||||||
|
|
||||||
|
public BigInteger getAmountOfJudges();
|
||||||
|
|
||||||
|
public Integer getAmountOfProposals();
|
||||||
|
}
|
20
src/main/java/fr/mieuxvoter/mj/UnbalancedTallyException.java
Normal file
20
src/main/java/fr/mieuxvoter/mj/UnbalancedTallyException.java
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package fr.mieuxvoter.mj;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raised when the provided tally does not hold the same amount of judgments for each proposal, and
|
||||||
|
* normalization is required.
|
||||||
|
*/
|
||||||
|
class UnbalancedTallyException extends InvalidTallyException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 5041093000505081735L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return ("The provided tally is unbalanced, as some proposals received more judgments than"
|
||||||
|
+ " others. \n"
|
||||||
|
+ "You need to set a strategy for balancing tallies. To that effect, \n"
|
||||||
|
+ "you may use StaticDefaultTally, MedianDefaultTally, or NormalizedTally"
|
||||||
|
+ " instead of Tally. \n"
|
||||||
|
+ (null == super.getMessage() ? "" : super.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue