/* * Copyright (C) 2017 John Garner * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.pikatimer.participant; import com.pikatimer.event.Event; import com.pikatimer.race.RaceDAO; import com.pikatimer.race.Wave; import static java.lang.Boolean.FALSE; import java.time.LocalDate; import java.time.Period; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import javafx.application.Platform; import javafx.beans.Observable; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.StringBinding; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.beans.property.ListProperty; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; import javafx.util.Callback; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.MapKeyColumn; import javax.persistence.Table; import javax.persistence.Transient; import org.hibernate.annotations.DynamicUpdate; import org.hibernate.annotations.GenericGenerator; /** * * @author jcgarner */ @Entity @DynamicUpdate @Table(name="PARTICIPANT") public class Participant { private final StringProperty firstNameProperty = new SimpleStringProperty(""); private final StringProperty middleNameProperty= new SimpleStringProperty(); private final StringProperty lastNameProperty= new SimpleStringProperty(""); private final StringProperty fullNameProperty= new SimpleStringProperty(); private final StringProperty emailProperty= new SimpleStringProperty(); private final IntegerProperty IDProperty = new SimpleIntegerProperty(); private final StringProperty uuidProperty = new SimpleStringProperty(java.util.UUID.randomUUID().toString()); private final StringProperty bibProperty= new SimpleStringProperty(); private final IntegerProperty ageProperty = new SimpleIntegerProperty(); private final StringProperty sexProperty= new SimpleStringProperty(); private final StringProperty cityProperty= new SimpleStringProperty(); private final StringProperty stateProperty= new SimpleStringProperty(); private final StringProperty countryProperty= new SimpleStringProperty(); private final StringProperty zipProperty = new SimpleStringProperty(); private LocalDate birthday; private final ObjectProperty<LocalDate> birthdayProperty = new SimpleObjectProperty(); private final ObservableList<Wave> waves = FXCollections.observableArrayList(Wave.extractor()); private final IntegerProperty wavesChangedCounterProperty = new SimpleIntegerProperty(0); private final ObjectProperty<ObservableList<Wave>> wavesProperty = new SimpleObjectProperty(waves); private Set<Integer> waveIDSet = new HashSet(); private final BooleanProperty dnfProperty = new SimpleBooleanProperty(FALSE); private final BooleanProperty dqProperty = new SimpleBooleanProperty(FALSE); private final StringProperty noteProperty = new SimpleStringProperty(); private Status status = Status.GOOD; private final ObjectProperty<Status> statusProperty = new SimpleObjectProperty(Status.GOOD); private final ObservableMap<Integer,StringProperty> customAttributeObservableMap = FXCollections.observableHashMap(); private Map<Integer,String> customAttributeMap = new HashMap(); public Participant() { fullNameProperty.bind(new StringBinding(){ {super.bind(firstNameProperty,middleNameProperty, lastNameProperty);} @Override protected String computeValue() { return (firstNameProperty.getValueSafe() + " " + middleNameProperty.getValueSafe() + " " + lastNameProperty.getValueSafe()).replaceAll("( )+", " "); } }); //Convenience properties for the getDNF and getDQ status checks dnfProperty.bind(new BooleanBinding(){ {super.bind(statusProperty);} @Override protected boolean computeValue() { if (statusProperty.getValue().equals(Status.DNF)) return true; return false; } }); dqProperty.bind(new BooleanBinding(){ {super.bind(statusProperty);} @Override protected boolean computeValue() { if (statusProperty.getValue().equals(Status.DQ)) return true; return false; } }); waves.addListener(new ListChangeListener<Wave>() { @Override public void onChanged(Change<? extends Wave> c) { Platform.runLater(() -> wavesChangedCounterProperty.setValue(wavesChangedCounterProperty.get()+1)); } }); status = Status.GOOD; statusProperty.set(status); } public Participant(Map<String, String> attribMap) { this(); setAttributes(attribMap); } public Participant(String firstName, String lastName) { this(); setFirstName(firstName); setLastName(lastName); } public static ObservableMap<String,String> getAvailableAttributes() { ObservableMap<String,String> attribMap = FXCollections.observableMap(new LinkedHashMap() ); attribMap.put("bib", "Bib"); attribMap.put("first", "First Name"); attribMap.put("middle", "Middle Name"); attribMap.put("last", "Last Name"); attribMap.put("age", "Age"); attribMap.put("birth","Birthday"); attribMap.put("sex-gender", "Sex"); attribMap.put("city", "City"); attribMap.put("state", "State"); attribMap.put("zip","Zip Code"); attribMap.put("country", "Country"); attribMap.put("status","Status"); attribMap.put("note","Note"); attribMap.put("email", "EMail"); // TODO: routine to add custom attributes based on db lookup return attribMap; } @ElementCollection(fetch = FetchType.EAGER) @MapKeyColumn(name="attribute_id") @Column(name="attribute_value") @CollectionTable(name="participant_attributes", [email protected](name="participant_id")) public Map<Integer,String> getCustomAttributes(){ return customAttributeMap; } public void setCustomAttributes(Map<Integer,String> attribMap) { customAttributeMap = attribMap; customAttributeMap.keySet().forEach(k -> { if (customAttributeObservableMap.containsKey(k)) { customAttributeObservableMap.get(k).set(customAttributeMap.get(k)); } else { customAttributeObservableMap.put(k, new SimpleStringProperty(customAttributeMap.get(k))); } }); } public ObservableMap<Integer,StringProperty> customAttributesProperty(){ return customAttributeObservableMap; } public StringProperty getCustomAttribute(Integer cID) { if (! customAttributeObservableMap.containsKey(cID)) { customAttributeObservableMap.put(cID, new SimpleStringProperty()); } return customAttributeObservableMap.get(cID); } public void setCustomAttribute(Integer cID, String value){ customAttributeMap.put(cID, value); if (customAttributeObservableMap.containsKey(cID)) { customAttributeObservableMap.get(cID).set(value); } else { customAttributeObservableMap.put(cID, new SimpleStringProperty(value)); } } public void setAttributes(Map<String, String> attribMap) { // bulk set routine. Everything is a string so convert as needed attribMap.entrySet().stream().forEach((Map.Entry<String, String> entry) -> { if (entry.getKey() != null) { //System.out.println("processing " + entry.getKey() ); switch(entry.getKey()) { case "bib": this.setBib(entry.getValue()); break; case "first": this.setFirstName(entry.getValue()); break; case "middle": this.setMiddleName(entry.getValue()); break; case "last": this.setLastName(entry.getValue()); break; case "birth": this.setBirthday(entry.getValue()); //set the age too if we were able to parse the birthdate break; case "age": //Setting the birthdate will also set the age, so if the age is already set just skip it. try { if (this.birthday == null) this.setAge(Integer.parseUnsignedInt(entry.getValue())); } catch (Exception e) { System.out.println("Unable to parse age " + entry.getValue() ); } break; // TODO: map to selected sex translator case "sex-gender": this.setSex(entry.getValue()); break; case "city": this.setCity(entry.getValue()); break; case "state": this.setState(entry.getValue()); break; case "country": this.setCountry(entry.getValue()); break; case "zip": this.setZip(entry.getValue()); break; case "note": this.setNote(entry.getValue()); break; case "status": try { this.setStatus(Status.valueOf(entry.getValue())); } catch (Exception e){ } case "email": this.setEmail(entry.getValue()); break; // TODO: Team value } } }); } public String getNamedAttribute(String attribute) { if (attribute != null) { //System.out.println("processing " + entry.getKey() ); switch(attribute) { case "bib": return this.bibProperty.getValueSafe(); case "first": return this.firstNameProperty.getValueSafe(); case "middle": return this.middleNameProperty.getValueSafe(); case "last": return this.lastNameProperty.getValueSafe(); // TODO: catch bad integers case "birth": if (this.birthday != null) return birthday.format(DateTimeFormatter.ISO_DATE); else return ""; case "age": if (this.ageProperty.getValue() != null) return this.ageProperty.getValue().toString(); else return ""; // TODO: map to selected sex translator case "sex-gender": return this.sexProperty.getValueSafe(); case "city": return this.cityProperty.getValueSafe(); case "state": return this.stateProperty.getValueSafe(); case "country": return this.countryProperty.getValueSafe(); case "zip": return this.zipProperty.getValueSafe(); case "note": return this.noteProperty.getValueSafe(); case "status": if (status != null) return status.name(); else return Status.GOOD.name(); case "email": return this.emailProperty.getValueSafe(); // TODO: Team value } } return ""; } @Id @GenericGenerator(name="participant_id" , strategy="increment") @GeneratedValue(generator="participant_id") @Column(name="PARTICIPANT_ID") public Integer getID() { return IDProperty.getValue(); } public void setID(Integer id) { IDProperty.setValue(id); } public IntegerProperty idProperty() { return IDProperty; } @Column(name="uuid") public String getUUID() { // System.out.println("Participant UUID is " + uuidProperty.get()); return uuidProperty.getValue(); } public void setUUID(String uuid) { uuidProperty.setValue(uuid); //System.out.println("Participant UUID is now " + uuidProperty.get()); } public StringProperty uuidProperty() { return uuidProperty; } @Column(name="FIRST_NAME") public String getFirstName() { return firstNameProperty.getValueSafe(); } public void setFirstName(String fName) { firstNameProperty.setValue(fName); } public StringProperty firstNameProperty() { return firstNameProperty; } @Column(name="LAST_NAME") public String getLastName() { return lastNameProperty.getValueSafe(); } public void setLastName(String fName) { lastNameProperty.setValue(fName); } public StringProperty lastNameProperty() { return lastNameProperty; } @Column(name="MIDDLE_NAME") public String getMiddleName() { return middleNameProperty.getValueSafe(); } public void setMiddleName(String mName) { middleNameProperty.setValue(mName); } public StringProperty middleNameProperty() { return middleNameProperty; } public StringProperty fullNameProperty(){ return fullNameProperty; } @Column(name="EMAIL") public String getEmail() { return emailProperty.getValueSafe(); } public void setEmail(String fName) { emailProperty.setValue(fName); } public StringProperty emailProperty() { return emailProperty; } @Column(name="BIB_Number") public String getBib() { return bibProperty.getValueSafe(); } public void setBib(String b) { bibProperty.setValue(b); } public StringProperty bibProperty() { return bibProperty; } @Column(name="AGE") public Integer getAge () { return ageProperty.getValue(); } public void setAge (Integer a) { ageProperty.setValue(a); } public IntegerProperty ageProperty() { return ageProperty; } @Column(name="SEX") public String getSex() { return sexProperty.getValueSafe(); } public void setSex(String s) { //Set to an upper case M or F for now //TODO: Switch this to the allowable values for a SEX if (s == null) return; if (s.startsWith("M") || s.startsWith("m")) sexProperty.setValue("M"); else if (s.startsWith("F") || s.startsWith("f")) sexProperty.setValue("F"); else sexProperty.setValue(s); } public StringProperty sexProperty() { return sexProperty; } @Column(name="CITY") public String getCity() { return cityProperty.getValueSafe(); } public void setCity(String c) { cityProperty.setValue(c); } public StringProperty cityProperty() { return cityProperty; } @Column(name="STATE") public String getState() { return stateProperty.getValueSafe(); } public void setState(String s) { stateProperty.setValue(s); } public StringProperty stateProperty(){ return stateProperty; } @Column(name="ZIP") public String getZip() { return zipProperty.getValueSafe(); } public void setZip(String s) { zipProperty.setValue(s); } public StringProperty zipProperty(){ return zipProperty; } @Column(name="COUNTRY") public String getCountry() { return countryProperty.getValueSafe(); } public void setCountry(String s) { countryProperty.setValue(s); } public StringProperty countryProperty(){ return countryProperty; } @Column(name="BIRTHDAY",nullable=true) public String getBirthday() { if (birthday != null) { //return Date.from(birthdayProperty.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()); return birthday.toString(); } else { return null; } } public void setBirthday(LocalDate d) { if (d != null) { birthday = d; birthdayProperty.setValue(d); } } public void setBirthday(String d) { //System.out.println("Birthdate String: " + d); if (d != null) { //Try and parse the date // First try the ISO_LOCAL_DATE (YYYY-MM-DD) // Then try and catch localized date strings such as MM/DD/YYYY // finally a last ditch effort for things like MM.DD.YYYY try{ birthday = LocalDate.parse(d,DateTimeFormatter.ISO_LOCAL_DATE); //System.out.println("Parsed via ISO_LOCAL_DATE: " + d); } catch (Exception e){ try { birthday = LocalDate.parse(d,DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)); //System.out.println("FormatStyle.SHORT: " + d); } catch (Exception e2){ try { birthday = LocalDate.parse(d,DateTimeFormatter.ofPattern("M/d/yyyy")); // System.out.println("Parsed via M/d/yyyy: " + d); } catch (Exception e3) { //System.out.println("Unble to parse date: " + d); } } } if (this.birthday != null) { birthdayProperty.setValue(birthday); this.setAge(Period.between(this.birthday, Event.getInstance().getLocalEventDate()).getYears()); //System.out.println("Parsed Date: " + d + " -> " + getAge()); } //Instant instant = Instant.ofEpochMilli(d.getTime()); //setBirthday(LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate()); } } public ObjectProperty<LocalDate> birthdayProperty() { return birthdayProperty; } @Transient public Boolean getDNF() { return dnfProperty.getValue(); } public BooleanProperty dnfProperty(){ return dnfProperty; } @Transient public Boolean getDQ() { return dqProperty.getValue(); } public BooleanProperty dqProperty(){ return dqProperty; } @Column(name="note", nullable=true) public String getNote() { return noteProperty.getValueSafe(); } public void setNote(String s) { noteProperty.setValue(s); } public StringProperty noteProperty(){ return noteProperty; } @Enumerated(EnumType.STRING) @Column(name="status") public Status getStatus() { return status; } public void setStatus(Status s) { if (s != null && (status == null || ! status.equals(s)) ){ status = s; statusProperty.set(status); } } public ObjectProperty<Status> statusProperty(){ return statusProperty; } @ElementCollection(fetch = FetchType.EAGER) @Column(name="wave_id", nullable=false) @CollectionTable(name="part2wave", [email protected](name="participant_id")) // @OrderColumn(name = "index_id") public Set<Integer> getWaveIDs() { return waveIDSet; } public void setWaveIDs(Set<Integer> w) { waveIDSet = w; } public void setWaves(List<Wave> w) { System.out.println("SetWaves(List) called with " + w.size()); waves.setAll(w); waveIDSet = new HashSet(); waves.stream().forEach(n -> {waveIDSet.add(n.getID());}); Platform.runLater(() -> wavesChangedCounterProperty.setValue(wavesChangedCounterProperty.get()+1)); } public void setWaves(Set<Wave> w){ System.out.println("SetWaves(Set) called with " + w.size()); waves.setAll(w); waveIDSet = new HashSet(); waves.stream().forEach(n -> {waveIDSet.add(n.getID());}); Platform.runLater(() -> wavesChangedCounterProperty.setValue(wavesChangedCounterProperty.get()+1)); } public void setWaves(Wave w) { System.out.println("Participant.setWaves(Wave w)"); waves.setAll(w); waveIDSet = new HashSet(); waveIDSet.add(w.getID()); Platform.runLater(() -> wavesChangedCounterProperty.setValue(wavesChangedCounterProperty.get()+1)); } public void addWave(Wave w) { //System.out.println("Participant.addWave(Wave w)"); if(w == null) System.out.println("Wave is NULL!!!"); else { //System.out.println("Participant.addWave(Wave w) " + w.getID()); waves.add(w); waveIDSet.add(w.getID()); } Platform.runLater(() -> wavesChangedCounterProperty.setValue(wavesChangedCounterProperty.get()+1)); } public ObservableList<Wave> wavesObservableList() { if (waves.size() != waveIDSet.size()){ waves.clear(); waveIDSet.stream().forEach(id -> { if (RaceDAO.getInstance().getWaveByID(id) == null) System.out.println("Null WAVE!!! " + id); waves.add(RaceDAO.getInstance().getWaveByID(id)); }); } return waves; } public ObjectProperty<ObservableList<Wave>> wavesProperty(){ return wavesProperty; } public IntegerProperty wavesChangedCounterProperty(){ return wavesChangedCounterProperty; } public static Callback<Participant, Observable[]> extractor() { return (Participant p) -> new Observable[]{p.firstNameProperty,p.middleNameProperty,p.lastNameProperty,p.bibProperty,p.ageProperty,p.sexProperty,p.cityProperty,p.stateProperty,p.countryProperty,p.wavesProperty,p.wavesChangedCounterProperty,p.statusProperty}; } @Override public int hashCode() { int hash = 5; hash = 89 * hash + Objects.hashCode(this.uuidProperty.getValue()); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Participant other = (Participant) obj; if (!Objects.equals(this.uuidProperty.getValue(),other.uuidProperty.getValue())) { return false; } return true; } }