/* * Copyright (C) 2017 John Garner <[email protected]> * * 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.race; import com.pikatimer.results.ProcessedResult; import com.pikatimer.participant.Participant; import com.pikatimer.participant.ParticipantDAO; import com.pikatimer.results.ResultsDAO; import com.pikatimer.util.DurationFormatter; import com.pikatimer.util.DurationParser; import static java.lang.Boolean.FALSE; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javafx.beans.Observable; 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.collections.FXCollections; import javafx.collections.ObservableList; import javafx.util.Callback; import javafx.util.Pair; 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.ManyToOne; import javax.persistence.Table; import org.hibernate.annotations.DynamicUpdate; import org.hibernate.annotations.GenericGenerator; /** * * @author John Garner <[email protected]> */ @Entity @DynamicUpdate @Table(name="race_award_categories") public class AwardCategory { private RaceAwards raceAward; @Override public String toString() { return nameProperty.getValueSafe(); } private final IntegerProperty IDProperty = new SimpleIntegerProperty(); private final StringProperty uuidProperty = new SimpleStringProperty(java.util.UUID.randomUUID().toString()); private final StringProperty nameProperty = new SimpleStringProperty("New Award"); private final IntegerProperty priorityProperty = new SimpleIntegerProperty(); private final ObjectProperty<AwardCategoryType> typeProperty = new SimpleObjectProperty(AwardCategoryType.OVERALL); private final ObjectProperty<AwardDepthType> depthTypeProperty = new SimpleObjectProperty(AwardDepthType.FIXED); private final BooleanProperty pullProperty = new SimpleBooleanProperty(true); private final BooleanProperty chipProperty = new SimpleBooleanProperty(true); private final BooleanProperty visibleAwardsProperty = new SimpleBooleanProperty(true); private final BooleanProperty visibleOverallProperty = new SimpleBooleanProperty(true); private final IntegerProperty depthProperty = new SimpleIntegerProperty(3); private final IntegerProperty mastersAgeProperty = new SimpleIntegerProperty(40); private final ObservableList<AwardDepth> customDepthObservableList = FXCollections.observableArrayList(AwardDepth.extractor()); private List<AwardFilter> filters; private Set<String> splitBy; private List<AwardDepth> customDepthList; // Custom Type Attributes private final BooleanProperty customFilteredProperty = new SimpleBooleanProperty(false); private final ObservableList<AwardFilter> filtersObservableList = FXCollections.observableArrayList(AwardFilter.extractor()); private final IntegerProperty timingPointIDProperty = new SimpleIntegerProperty(0); private final StringProperty timingPointTypeProperty = new SimpleStringProperty("FINISH"); private final BooleanProperty customSubdivideProperty = new SimpleBooleanProperty(false); private final ObservableList<String> subdivideListProperty = FXCollections.observableArrayList(); private final BooleanProperty skewedProperty = new SimpleBooleanProperty(false); private final StringProperty skewOpProperty = new SimpleStringProperty("ADD"); private final IntegerProperty skewAttributeProperty = new SimpleIntegerProperty(-1); public AwardCategory() { } @Id @GenericGenerator(name="award_category_id" , strategy="increment") @GeneratedValue(generator="award_category_id") @Column(name="ID") public Integer getID() { return IDProperty.getValue(); } public void setID(Integer id) { IDProperty.setValue(id); } public IntegerProperty idProperty() { return IDProperty; } // uuid varchar, @Column(name="uuid") public String getUUID() { // System.out.println("RaceReport UUID is " + uuidProperty.get()); return uuidProperty.getValue(); } public void setUUID(String uuid) { uuidProperty.setValue(uuid); //System.out.println("RaceReport UUID is now " + uuidProperty.get()); } public StringProperty uuidProperty() { return uuidProperty; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "RACE_ID",nullable=false) public RaceAwards getRaceAward() { return raceAward; } public void setRaceAward(RaceAwards r) { raceAward=r; } @Column(name="category_name") public String getName() { //System.out.println("AgeGroups.getAGIncrement() returning " + agIncrement); return nameProperty.getValueSafe(); } public void setName(String i) { nameProperty.setValue(i); } public StringProperty nameProperty() { return nameProperty; } @Column(name="category_priority") public Integer getPriority() { //System.out.println("AgeGroups.getAGIncrement() returning " + agIncrement); return priorityProperty.getValue(); } public void setPriority(Integer i) { priorityProperty.setValue(i); } public IntegerProperty priorityProperty() { return priorityProperty; } @Enumerated(EnumType.STRING) @Column(name="award_type") public AwardCategoryType getType(){ return typeProperty.getValue(); } public void setType(AwardCategoryType t) { typeProperty.setValue(t); } public ObjectProperty<AwardCategoryType> typeProperty(){ return typeProperty; } @Column(name="pull") public Boolean getPull(){ return pullProperty.getValue(); } public void setPull(Boolean t) { pullProperty.setValue(t); } public BooleanProperty pullProperty(){ return pullProperty; } @Column(name="chip") public Boolean getChip(){ return chipProperty.getValue(); } public void setChip(Boolean t) { chipProperty.setValue(t); } public BooleanProperty chipProperty(){ return chipProperty; } //visibleAwardsProperty @Column(name="visible") public Boolean getVisible(){ return visibleAwardsProperty.getValue(); } public void setVisible(Boolean t) { visibleAwardsProperty.setValue(t); } public BooleanProperty visibleProperty(){ return visibleAwardsProperty; } //visibleOverallProperty @Column(name="visible_overall") public Boolean getVisibleOverall(){ return visibleOverallProperty.getValue(); } public void setVisibleOverall(Boolean t) { visibleOverallProperty.setValue(t); } public BooleanProperty visibleOverallProperty(){ return visibleOverallProperty; } //timing_point_type varchar, @Column(name="timing_point_type") public String getTimingPointType() { //System.out.println("AgeGroups.getAGIncrement() returning " + agIncrement); return timingPointTypeProperty.getValueSafe(); } public void setTimingPointType(String i) { timingPointTypeProperty.setValue(i); } public StringProperty timingPointTypeProperty() { return timingPointTypeProperty; } //timing_point_value int, @Column(name="timing_point_value") public Integer getTimingPointID() { //System.out.println("AgeGroups.getAGIncrement() returning " + agIncrement); return timingPointIDProperty.getValue(); } public void setTimingPointID(Integer i) { timingPointIDProperty.setValue(i); } public IntegerProperty timingPointIDProperty() { return timingPointIDProperty; } @Enumerated(EnumType.STRING) @Column(name="depth_type") public AwardDepthType getDepthType(){ return depthTypeProperty.getValue(); } public void setDepthType(AwardDepthType t) { depthTypeProperty.setValue(t); } public ObjectProperty<AwardDepthType> depthTypeProperty(){ return depthTypeProperty; } @Column(name="category_depth") public Integer getDepth() { System.out.println("AwardCategory.getDepth(): returning " + depthProperty.getValue()); return depthProperty.getValue(); } public void setDepth(Integer i) { System.out.println("AwardCategory.setDepth(): " + i); depthProperty.setValue(i); } public IntegerProperty depthProperty() { return depthProperty; } @Column(name="masters_age") public Integer getMastersAge() { System.out.println("AwardCategory.mastersAge(): returning " + mastersAgeProperty.getValue()); return mastersAgeProperty.getValue(); } public void setMastersAge(Integer i) { System.out.println("AwardCategory.setDepth(): " + i); mastersAgeProperty.setValue(i); } public IntegerProperty mastersAgeProperty() { return mastersAgeProperty; } @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name="race_award_category_depths", joinColumns=@JoinColumn(name="ac_id") ) protected List<AwardDepth> getCustomDepthList(){ return customDepthList; } protected void setCustomDepthList(List<AwardDepth> i){ customDepthList = i; } public ObservableList<AwardDepth> customDepthProperty(){ if (customDepthObservableList.isEmpty() && customDepthList != null && ! customDepthList.isEmpty() ) { customDepthObservableList.addAll(customDepthList); recalcCustomDepths(); } return customDepthObservableList; } public void addCustomDepth(AwardDepth i){ System.out.println("addCustomDepth called"); customDepthObservableList.add(i); recalcCustomDepths(); customDepthList = customDepthObservableList; } public void removeCustomDepth(AwardDepth i){ customDepthObservableList.remove(i); recalcCustomDepths(); customDepthList = customDepthObservableList; } public void recalcCustomDepths(){ customDepthObservableList.sort((i1, i2) -> i1.getStartCount().compareTo(i2.getStartCount())); for (int i=0; i < customDepthObservableList.size(); i++) { if (i == customDepthObservableList.size() -1) customDepthObservableList.get(i).endCountProperty().setValue("∞"); else customDepthObservableList.get(i).endCountProperty().setValue(Integer.toString(customDepthObservableList.get(i+1).getStartCount() - 1)); } } @Column(name="filter") public Boolean getFiltered(){ return customFilteredProperty.getValue(); } public void setFiltered(Boolean t) { customFilteredProperty.setValue(t); } public BooleanProperty filteredProperty(){ return customFilteredProperty; } @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name="race_award_category_filters", joinColumns=@JoinColumn(name="ac_id") ) protected List<AwardFilter> getFilterList(){ return filters; } protected void setFilterList(List<AwardFilter> i){ filters = i; } public ObservableList<AwardFilter> filtersProperty(){ if (filtersObservableList.isEmpty() && filters != null && ! filters.isEmpty() ) { filtersObservableList.addAll(filters); } return filtersObservableList; } public void addFilter(AwardFilter a){ filtersObservableList.add(a); filters = filtersObservableList; } public void deleteFilter(AwardFilter a){ filtersObservableList.remove(a); filters = filtersObservableList; } @Column(name="subdivide") public Boolean getSubdivided(){ return customSubdivideProperty.getValue(); } public void setSubdivided(Boolean t) { customSubdivideProperty.setValue(t); } public BooleanProperty subdivideProperty(){ return customSubdivideProperty; } @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name="race_award_category_subdivide_list", joinColumns=@JoinColumn(name="ac_id") ) @Column(name="attribute") protected Set<String> getSubDivideList(){ return splitBy; } protected void setSubDivideList(Set<String> i){ splitBy = i; } public ObservableList<String> subDivideProperty(){ if (subdivideListProperty.isEmpty() && splitBy != null && ! splitBy.isEmpty() ) { subdivideListProperty.addAll(splitBy); } return subdivideListProperty; } public void updateSubdivideList(){ splitBy = new HashSet(); splitBy.addAll(subdivideListProperty); splitBy.forEach(s -> {System.out.println("AwardCategory subdivide category: " + s);}); } @Column(name="skew") public Boolean getSkewed(){ return skewedProperty.getValue(); } public void setSkewed(Boolean t) { skewedProperty.setValue(t); } public BooleanProperty skewedProperty(){ return skewedProperty; } @Column(name="skew_type") public String getSkewType() { //System.out.println("AgeGroups.getAGIncrement() returning " + agIncrement); return skewOpProperty.getValueSafe(); } public void setSkewType(String i) { skewOpProperty.setValue(i); } public StringProperty skewTypeProperty() { return skewOpProperty; } @Column(name="skew_attribute") public Integer getSkewAttribute() { return skewAttributeProperty.getValue(); } public void setSkewAttribute(Integer id) { skewAttributeProperty.setValue(id); } public IntegerProperty skewAttributeProperty() { return skewAttributeProperty; } public Pair<Map<String,List<AwardWinner>>,List<ProcessedResult>> process(List<ProcessedResult> pr){ List<AwardFilter> processFilters; List<String> processSplitBy; IntegerProperty timeID = new SimpleIntegerProperty(0); Race race = raceAward.getRace(); System.out.println("Processing " + typeProperty.toString() + " " + nameProperty.getValueSafe()); // What is going on here... // The "pr" list is the contenders for the award // Let's make a copy for downstream contenders List<ProcessedResult> downstreamContenders =new ArrayList(pr); // We send back a Pair consisting for a map of the subCategory to the winners // and a list of results elliglble for downstream awards. // if we are not a Custom award type, setup some default // filters and splitBy arrays. switch (typeProperty.get()) { case OVERALL: // no Filter processFilters = new ArrayList(); processSplitBy = Arrays.asList("sex"); timingPointTypeProperty.setValue("FINISH"); break; case MASTERS: processFilters= new ArrayList(); processFilters.add(new AwardFilter("age",">=",mastersAgeProperty.getValue().toString())); processSplitBy = Arrays.asList("sex"); timingPointTypeProperty.setValue("FINISH"); break; case AGEGROUP: processFilters= new ArrayList(); processSplitBy = Arrays.asList("sex","AG"); timingPointTypeProperty.setValue("FINISH"); break; default: System.out.println("Custom Award: " + typeProperty.getName()); if (filters == null || customFilteredProperty.equals(FALSE)) processFilters = new ArrayList(); else processFilters = filters; if (splitBy == null || customSubdivideProperty.equals(FALSE)) processSplitBy = new ArrayList(); else {processSplitBy = new ArrayList(); processSplitBy.addAll(splitBy);} if (timingPointTypeProperty.getValueSafe().equals("SPLIT")) { race.getSplits().forEach(s -> { if (s.getID().equals(timingPointIDProperty.get())) timeID.setValue(s.getPosition()); }); } else if (timingPointTypeProperty.getValueSafe().equals("SEGMENT")) { timeID.setValue(timingPointIDProperty.get()); } break; } // Step 1: filter // We assume that the list we have already filtered // all DNF's, DQ's, and folks with no finish times. // Then filter by whatever the overall filter is (if any); // The result our own copy to screw with Duration cutoffTime = Duration.ofNanos(race.getRaceCutoff()); String dispFormat = race.getStringAttribute("TimeDisplayFormat"); String roundMode = race.getStringAttribute("TimeRoundingMode"); String cutoffTimeString = DurationFormatter.durationToString(cutoffTime, dispFormat, roundMode); List<ProcessedResult> contendersList = new ArrayList( pr.stream().filter(p -> { for(int i=0; i< processFilters.size(); i++){ if (processFilters.get(i).filter(p,race) == false) return false; } if (timingPointTypeProperty.getValueSafe().equals("SPLIT")) { if (p.getSplit(timeID.get())== null) return false; } else if (timingPointTypeProperty.getValueSafe().equals("SEGMENT")) { if (p.getSegmentTime(timeID.get())== null) return false; } return true; }) .sorted((p1, p2) -> p1.getChipFinish().compareTo(p2.getChipFinish())) .collect(Collectors.toList()) ); // Step 2: sort by award time contendersList.sort((p1,p2) -> { Duration p1Time = Duration.ZERO; Duration p2Time = Duration.ZERO; if(skewedProperty.get()) { if(DurationParser.parsable(p1.getParticipant().getCustomAttribute(skewAttributeProperty.get()).getValueSafe())) p1Time = DurationParser.parse(p1.getParticipant().getCustomAttribute(skewAttributeProperty.get()).getValueSafe()); if(DurationParser.parsable(p2.getParticipant().getCustomAttribute(skewAttributeProperty.get()).getValueSafe())) p2Time = DurationParser.parse(p2.getParticipant().getCustomAttribute(skewAttributeProperty.get()).getValueSafe()); if (skewOpProperty.get().equals("-")) { p1Time = p1Time.negated(); p2Time = p2Time.negated(); } } if (timingPointTypeProperty.getValueSafe().equals("SPLIT")) { p1Time = p1Time.plus(p1.getSplit(timeID.get())); p2Time = p2Time.plus(p2.getSplit(timeID.get())); } else if (timingPointTypeProperty.getValueSafe().equals("SEGMENT")) { p1Time = p1Time.plus(p1.getSegmentTime(timeID.get())); p2Time = p2Time.plus(p2.getSegmentTime(timeID.get())); } else { if (chipProperty.get()) { p1Time = p1Time.plus(p1.getChipFinish()); p2Time = p2Time.plus(p2.getChipFinish()); } else { p1Time = p1Time.plus(p1.getGunFinish()); p2Time = p2Time.plus(p2.getGunFinish()); } } return p1Time.compareTo(p2Time); }); // Step 3: Split Map<String,List<ProcessedResult>> contendersMap = new HashMap(); contendersList.forEach(r -> { String splitCat = ""; // What is their split category string? for(int i=0; i<processSplitBy.size();i++){ String attrib = processSplitBy.get(i); if (attrib.startsWith("sex")) { if (r.getSex().startsWith("M")) splitCat += "Male "; else splitCat += "Female "; } else if (attrib.equals("AG")) { splitCat += r.getAGCode() + " "; } else if (attrib.matches("^\\d+$")) { // custom attribute try {splitCat += r.getParticipant().getCustomAttribute(Integer.parseInt(attrib)).getValueSafe() + " ";} catch (Exception e){} } else { splitCat += r.getParticipant().getNamedAttribute(attrib) + " "; } } splitCat = splitCat.trim(); if (!contendersMap.containsKey(splitCat)) contendersMap.put(splitCat,new ArrayList()); contendersMap.get(splitCat).add(r); }); // Step 4: calculate award depths Map<String,Integer> depthMap = new HashMap(); if (AwardDepthType.FIXED.equals(depthTypeProperty.get())) contendersMap.keySet().forEach(k -> {depthMap.put(k, depthProperty.getValue());}); else { // Oh god, // first, figure oout how many folks are in play List<Participant> part; if (AwardDepthType.BYREG.equals(depthTypeProperty.get())) part = ParticipantDAO.getInstance().listParticipants(); else part = ResultsDAO.getInstance().getResults(race.getID()).stream() .filter(p -> { for(int i=0; i< processFilters.size(); i++){ if (processFilters.get(i).filter(ParticipantDAO.getInstance().getParticipantByBib(p.getBib()),race) == false) return false; } return true; }) .map(p -> ParticipantDAO.getInstance().getParticipantByBib(p.getBib())) .collect(Collectors.toList()); // now sort them into subdivisions.... Map<String,Integer> subMap = new HashMap(); part.forEach(r -> { String splitCat = ""; // What is their split category string? for(int i=0; i<processSplitBy.size();i++){ String attrib = processSplitBy.get(i); if (attrib.startsWith("sex")) { if (r.getSex().startsWith("M")) splitCat += "Male "; else splitCat += "Female "; } else if (attrib.equals("AG")) { splitCat += race.getAgeGroups().ageToAGString(r.getAge()) + " "; } else if (attrib.matches("^\\d+$")) { // custom attribute try {splitCat += r.getCustomAttribute(Integer.parseInt(attrib)).getValueSafe() + " ";} catch (Exception e){} } else { splitCat += r.getNamedAttribute(attrib) + " "; } } splitCat = splitCat.trim(); if (!subMap.containsKey(splitCat)) subMap.put(splitCat,1); else subMap.put(splitCat, subMap.get(splitCat) + 1); }); // now create the depthMap based on the registration numbers contendersMap.keySet().forEach(k -> { Integer count = 0; if (!subMap.containsKey(k)) { System.out.println("Odd, we have a contender with a sub-type of k but no registered or starting participants"); } else { count = subMap.get(k); } subMap.get(k); Integer depth =0; for(AwardDepth d: customDepthList){ if(count >= d.getStartCount()) depth = d.getDepth(); } System.out.println("AwardDepth: for " + k + " we have " + count + " and will go " + depth); depthMap.put(k, depth); }); } // Step 5: divy up awards by subcategory Map<String,List<AwardWinner>> winners = new HashMap(); Boolean ties = race.getBooleanAttribute("permitTies"); contendersMap.keySet().forEach(cat -> { winners.put(cat, new ArrayList()); if (! contendersMap.containsKey(cat) || contendersMap.get(cat).isEmpty()) return; String lastTime=""; String currentTime=""; Integer currentPlace = 1; AwardWinner prevAW = null; for(int i=0; i<depthMap.get(cat) && i <contendersMap.get(cat).size(); i++) { if (i==0) { lastTime=""; currentPlace = 0; } AwardWinner a = new AwardWinner(); a.awardTime = Duration.ZERO; if(skewedProperty.get()) { if(DurationParser.parsable(contendersMap.get(cat).get(i).getParticipant().getCustomAttribute(skewAttributeProperty.get()).get())) a.awardTime = DurationParser.parse(contendersMap.get(cat).get(i).getParticipant().getCustomAttribute(skewAttributeProperty.get()).get()); if (skewOpProperty.get().equals("-")) { a.awardTime = a.awardTime.negated(); } } if (timingPointTypeProperty.getValueSafe().equals("SPLIT")) { a.awardTime = a.awardTime.plus(contendersMap.get(cat).get(i).getSplit(timeID.get())); } else if (timingPointTypeProperty.getValueSafe().equals("SEGMENT")) { a.awardTime = a.awardTime.plus(contendersMap.get(cat).get(i).getSegmentTime(timeID.get())); } else if (chipProperty.get()) a.awardTime = a.awardTime.plus(contendersMap.get(cat).get(i).getChipFinish()); else a.awardTime = a.awardTime.plus(contendersMap.get(cat).get(i).getGunFinish()); currentTime = DurationFormatter.durationToString(a.awardTime, dispFormat, roundMode); //System.out.println("Award::printWinners: Comparing previous " + lastTime + " to " + currentTime); if (ties && !lastTime.equals(currentTime)) currentPlace = i+1; else if (lastTime.equals(currentTime)) { prevAW.tie = true; a.tie = true;} else if (!ties) currentPlace++; a.participant = contendersMap.get(cat).get(i).getParticipant(); a.awardPlace = currentPlace; a.awardTitle = cat; a.processedResult = contendersMap.get(cat).get(i); winners.get(cat).add(a); lastTime = currentTime; prevAW = a; // If we are pulling then remove them from downstream awards if(pullProperty.get()) downstreamContenders.remove(contendersMap.get(cat).get(i)); if (ties && i == depthMap.get(cat)-1 && i+1 <contendersMap.get(cat).size()) { // last one.... String nextTime =""; if (chipProperty.get()) nextTime = DurationFormatter.durationToString(contendersMap.get(cat).get(i+1).getChipFinish(), dispFormat, roundMode); else nextTime = DurationFormatter.durationToString(contendersMap.get(cat).get(i+1).getGunFinish(), dispFormat, roundMode); if (currentTime.equals(nextTime)){ if (timingPointTypeProperty.getValueSafe().equals("SPLIT")) { a.awardTime = contendersMap.get(cat).get(i+1).getSplit(timeID.get()); } else if (timingPointTypeProperty.getValueSafe().equals("SEGMENT")) { a.awardTime = contendersMap.get(cat).get(i+1).getSegmentTime(timeID.get()); } else if (chipProperty.get()) a.awardTime = contendersMap.get(cat).get(i+1).getChipFinish(); else a.awardTime = contendersMap.get(cat).get(i+1).getGunFinish(); prevAW.tie = true; a = new AwardWinner(); a.tie = true; a.participant = contendersMap.get(cat).get(i+1).getParticipant(); a.awardPlace = currentPlace; a.awardTitle = cat; a.processedResult = contendersMap.get(cat).get(i+1); winners.get(cat).add(a); if(pullProperty.get()) downstreamContenders.remove(contendersMap.get(cat).get(i+1)); } } } }); return new Pair(winners,downstreamContenders); } public static Callback<AwardCategory, Observable[]> extractor() { return (AwardCategory ac) -> new Observable[]{ac.priorityProperty}; } }