package gov.nysenate.openleg.model.bill;
import gov.nysenate.openleg.model.agenda.CommitteeAgendaId;
import gov.nysenate.openleg.model.base.BaseLegislativeContent;
import gov.nysenate.openleg.model.base.PublishStatus;
import gov.nysenate.openleg.model.base.Version;
import gov.nysenate.openleg.model.calendar.CalendarId;
import gov.nysenate.openleg.model.entity.Chamber;
import gov.nysenate.openleg.model.entity.CommitteeVersionId;
import gov.nysenate.openleg.model.entity.SessionMember;
import gov.nysenate.openleg.service.bill.data.BillAmendNotFoundEx;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* The Bill class serves as a container for all the entities that can be classified under a print number
* and session year. It contains a collection of amendments (including the base amendment) as well as
* shared information such as the sponsor, actions, etc.
*/
public class Bill extends BaseLegislativeContent implements Serializable, Comparable<Bill>, Cloneable
{
private static final long serialVersionUID = 2925424993477789289L;
/** The base bill id which should be used to uniquely identify this instance. */
protected BaseBillId baseBillId;
/** Starting with the terms "An act", it's a short description about the topic of the bill. */
protected String title = "";
/** An overview of a bill that list's specific sections of NYS law to be amended by the bill. */
protected String summary = "";
/** The status of the bill which is derived via the actions list. */
protected BillStatus status;
/** A set of statuses that are considered milestones. */
protected List<BillStatus> milestones = Collections.synchronizedList(new LinkedList<>());
/** A mapping of amendment versions to BillAmendment instances (includes base amendment). */
protected Map<Version, BillAmendment> amendmentMap = Collections.synchronizedSortedMap(new TreeMap<>());
/** Publish status mapped by amendment versions. */
protected Map<Version, PublishStatus> amendPublishStatusMap =Collections.synchronizedSortedMap(new TreeMap<>());
/** A list of veto messages for this bill */
protected Map<VetoId, VetoMessage> vetoMessages = Collections.synchronizedMap(new HashMap<>());
/** An approval message for the bill, null if non existent */
protected ApprovalMessage approvalMessage = null;
/** Indicates the amendment version that is currently active for this bill. */
protected Version activeVersion = BillId.DEFAULT_VERSION;
/** The Legislator who formally introduced the bill. */
protected BillSponsor sponsor;
/** A list of co-sponsors that will be given preferential display treatment. */
protected List<SessionMember> additionalSponsors = Collections.synchronizedList(new ArrayList<>());
/** A list of committees this bill has been referred to. */
protected SortedSet<CommitteeVersionId> pastCommittees = Collections.synchronizedSortedSet(new TreeSet<>());
/** A list of actions that have been made on this bill. */
protected List<BillAction> actions = Collections.synchronizedList(new ArrayList<>());
/** If the bill has been substituted by another, store the reference of that bill's id. */
protected BaseBillId substitutedBy;
/** A list of ids for versions of this legislation in previous sessions.
* This set of will contain only previous versions that have been directly linked to this bill*/
protected Set<BillId> directPreviousVersions = Collections.synchronizedSortedSet(new TreeSet<>());
/** A list of ids for versions of this legislation in previous sessions.
* This set will contain all previous versions, even those that are indirectly linked
* e.g. the previous version of a previous version*/
protected Set<BillId> allPreviousVersions = Collections.synchronizedSortedSet(new TreeSet<>());
/** Designates the type of program bill, if applicable. */
protected ProgramInfo programInfo;
/** Links to committee agendas that involve this bill. */
protected List<CommitteeAgendaId> committeeAgendas = Collections.synchronizedList(new ArrayList<>());
/** Associated floor calendar ids. */
protected List<CalendarId> calendars = Collections.synchronizedList(new ArrayList<>());
/** Bills that are passed are assigned a chapter number. */
protected Integer chapterNum;
/** Year this bill was signed into law. */
protected Integer chapterYear;
/** --- Constructors --- */
public Bill() {}
public Bill(BaseBillId baseBillId) {
this.setBaseBillId(baseBillId);
this.setSession(baseBillId.getSession());
}
/** --- Overrides --- */
@Override
public int compareTo(Bill other) {
return this.getBaseBillId().compareTo(other.getBaseBillId());
}
/**
* Set the publish date of the bill container and use that to set the active year of the bill.
*/
@Override
public void setPublishedDateTime(LocalDateTime publishDateTime) {
super.setPublishedDateTime(publishDateTime);
if (this.publishedDateTime != null) {
// Sometimes bills are pre-filed before the session actually starts so we account for this.
super.setYear(Integer.max(this.session.getYear(), publishDateTime.getYear()));
}
else {
super.setYear(this.session.getYear());
}
}
@Override
public String toString() {
return this.getBaseBillId().toString();
}
/**
* Creates a shallow clone for caching purposes. This is not a true clone so references will stay
* intact except for the amendment list.
* @return Bill
* @throws CloneNotSupportedException
*/
public Bill shallowClone() throws CloneNotSupportedException {
Bill cloneBill = (Bill) this.clone();
cloneBill.amendmentMap = new TreeMap<>();
this.getAmendmentList().stream().forEach(a -> cloneBill.addAmendment(a.shallowClone()));
return cloneBill;
}
/** --- Functional Getters/Setters --- */
/**
* Return a bill info object for this bill
*/
public BillInfo getBillInfo() {
return new BillInfo(this);
}
/**
* Delegate to retrieve print no.
*/
public String getBasePrintNo() {
return this.getBaseBillId().getBasePrintNo();
}
/**
* Returns the BillType which contains info such as the prefix and chamber.
*/
public BillType getBillType() {
return this.getBaseBillId().getBillType();
}
/**
* @return the Chamber of this bill
*/
public Chamber getChamber() {
return this.baseBillId.getChamber();
}
/**
* Returns true if this bill is a resolution of some sort.
*/
public boolean isResolution() {
return getBillType().isResolution();
}
/**
* Retrieves an amendment stored in this bill using the version as the key.
*
* @param version - The amendment version of the bill (e.g "A", "B", etc)
* @return BillAmendment
* @throws BillAmendNotFoundEx if the bill amendment does not exist
*/
public BillAmendment getAmendment(Version version) throws BillAmendNotFoundEx {
if (this.hasAmendment(version)) {
return this.amendmentMap.get(version);
}
throw new BillAmendNotFoundEx(baseBillId.withVersion(version));
}
/**
* Retrieves a list of all amendments stored in this bill.
*/
public List<BillAmendment> getAmendmentList() {
return Collections.synchronizedList(new ArrayList<>(this.amendmentMap.values()));
}
/**
* @return a set containing the bill ids of this bill's amendments
*/
public SortedSet<BillId> getAmendmentIds() {
return Collections.synchronizedSortedSet(this.amendmentMap.values().stream()
.map(BillAmendment::getBillId)
.collect(Collectors.toCollection(TreeSet::new)));
}
/**
* Associate an amendment with this bill. Replaces existing instance of the same version.
*
* @param billAmendment - Amendment to add to this bill. Cannot be null.
*/
public void addAmendment(BillAmendment billAmendment) {
if (billAmendment != null) {
this.amendmentMap.put(billAmendment.getVersion(), billAmendment);
}
else {
throw new IllegalArgumentException("Supplied BillAmendment cannot be null.");
}
}
/**
* Associate a list of amendments with this bill.
*
* @param billAmendments - List<Amendment> - Amendments to add to this bill
*/
public void addAmendments(List<BillAmendment> billAmendments) {
billAmendments.forEach(this::addAmendment);
}
/**
* Retrieve a PublishStatus for a specific amendment version.
*
* @param version String - Amendment version
* @return Optional<PublishStatus> - Value will be set if mapping exists.
*/
public Optional<PublishStatus> getPublishStatus(Version version) {
if (this.amendPublishStatusMap.containsKey(version)) {
return Optional.of(this.amendPublishStatusMap.get(version));
}
return Optional.empty();
}
/**
* Associate the publish status to a particular bill amendment. The bill amendment
* instance does not necessarily have to exist prior to updating its publish status.
*
* @param version Version - Amendment version
* @param publishStatus PublishStatus - The publish status of the bill amendment
*/
public void updatePublishStatus(Version version, PublishStatus publishStatus) {
if (publishStatus != null) {
this.amendPublishStatusMap.put(version, publishStatus);
}
else {
throw new IllegalArgumentException("Supplied PublishStatus cannot be null.");
}
}
/**
* Clears the existing publishStatusMap and then delegates each entry to
* {@link #updatePublishStatus(Version, PublishStatus)}
*
* @param publishStatusMap Map<String, PublishStatus>
*/
public void setPublishStatuses(Map<Version, PublishStatus> publishStatusMap) {
this.amendPublishStatusMap.clear();
if (publishStatusMap != null) {
publishStatusMap.forEach(this::updatePublishStatus);
}
else {
throw new IllegalArgumentException("Supplied PublishStatusMap cannot be null.");
}
}
/**
* Indicates if the base amendment is published.
* @return boolean
*/
public boolean isBaseVersionPublished() {
return (this.amendPublishStatusMap.containsKey(Version.DEFAULT) &&
this.amendPublishStatusMap.get(Version.DEFAULT).isPublished());
}
/**
* Indicate whether the bill has a reference to a given amendment version.
*
* @param version String - Amendment version
* @return boolean - true if amendment exists, false otherwise
*/
public boolean hasAmendment(Version version) {
return this.amendmentMap.containsKey(version) &&
this.amendmentMap.get(version) != null;
}
/**
* Indicate if the bill has a reference to the active amendment version.
*/
public boolean hasActiveAmendment() {
return hasAmendment(this.activeVersion);
}
/**
* Sets the active version, creating a new BillAmendment instance if the reference does
* not exist.
*
* @param activeVersion Version
*/
public void setActiveVersion(Version activeVersion) {
this.activeVersion = activeVersion;
if (!this.amendmentMap.containsKey(activeVersion)) {
this.amendmentMap.put(activeVersion, new BillAmendment(this.baseBillId, activeVersion));
}
}
/**
* Convenience method to retrieve the currently active Amendment object.
*
* @return BillAmendment
* @throws BillAmendNotFoundEx if the bill amendment does not exist
*/
public BillAmendment getActiveAmendment() throws BillAmendNotFoundEx {
return this.getAmendment(this.getActiveVersion());
}
/**
* Add the bill id to the previous bill versions set.
*/
public void addDirectPreviousVersion(BillId previousVersion) {
directPreviousVersions.add(previousVersion);
}
/**
* Add an action to the list of actions.
*/
public void addAction(BillAction action) {
actions.add(action);
}
/**
* Adds a committee to the list of past committees.
*/
public void addPastCommittee(CommitteeVersionId committeeVersionId) {
pastCommittees.add(committeeVersionId);
}
/** --- Delegates --- */
public String getFullText() {
if (this.hasActiveAmendment()) {
return this.getActiveAmendment().getFullText();
}
return "";
}
/** --- Basic Getters/Setters --- */
public BaseBillId getBaseBillId() {
return baseBillId;
}
public void setBaseBillId(BaseBillId baseBillId) {
this.baseBillId = baseBillId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public Version getActiveVersion() {
return activeVersion;
}
public Map<Version, BillAmendment> getAmendmentMap() {
return amendmentMap;
}
public Map<Version, PublishStatus> getAmendPublishStatusMap() {
return amendPublishStatusMap;
}
public BillStatus getStatus() {
return status;
}
public void setStatus(BillStatus status) {
this.status = status;
}
public List<BillStatus> getMilestones() {
return milestones;
}
public void setMilestones(LinkedList<BillStatus> milestones) {
this.milestones = milestones;
}
public Map<VetoId,VetoMessage> getVetoMessages() {
return vetoMessages;
}
public void setVetoMessages(Map<VetoId,VetoMessage> vetoMessages) {
this.vetoMessages = vetoMessages;
}
public ApprovalMessage getApprovalMessage() {
return approvalMessage;
}
public void setApprovalMessage(ApprovalMessage approvalMessage) {
this.approvalMessage = approvalMessage;
}
public Set<BillId> getDirectPreviousVersions() {
return directPreviousVersions;
}
public void setDirectPreviousVersions(Set<BillId> directPreviousVersions) {
this.directPreviousVersions = directPreviousVersions;
}
public Set<BillId> getAllPreviousVersions() {
return allPreviousVersions;
}
public void setAllPreviousVersions(Set<BillId> previousVersions) {
this.allPreviousVersions = previousVersions;
}
public BaseBillId getSubstitutedBy() {
return substitutedBy;
}
public void setSubstitutedBy(BaseBillId substitutedBy) {
this.substitutedBy = substitutedBy;
}
public List<BillAction> getActions() {
return actions;
}
public void setActions(List<BillAction> actions) {
this.actions = actions;
}
public BillSponsor getSponsor() {
return sponsor;
}
public void setSponsor(BillSponsor sponsor) {
this.sponsor = sponsor;
}
public SortedSet<CommitteeVersionId> getPastCommittees() {
return pastCommittees;
}
public void setPastCommittees(SortedSet<CommitteeVersionId> pastCommittees) {
this.pastCommittees = pastCommittees;
}
public List<SessionMember> getAdditionalSponsors() {
return additionalSponsors;
}
public void setAdditionalSponsors(List<SessionMember> additionalSponsors) {
this.additionalSponsors = additionalSponsors;
}
public ProgramInfo getProgramInfo() {
return programInfo;
}
public void setProgramInfo(ProgramInfo programInfo) {
this.programInfo = programInfo;
}
public List<CommitteeAgendaId> getCommitteeAgendas() {
return committeeAgendas;
}
public void setCommitteeAgendas(List<CommitteeAgendaId> committeeAgendas) {
this.committeeAgendas = committeeAgendas;
}
public List<CalendarId> getCalendars() {
return calendars;
}
public void setCalendars(List<CalendarId> calendars) {
this.calendars = calendars;
}
public Integer getChapterNum() {
return chapterNum;
}
public void setChapterNum(Integer chapterNum) {
this.chapterNum = chapterNum;
}
public Integer getChapterYear() {
return chapterYear;
}
public void setChapterYear(Integer chapterYear) {
this.chapterYear = chapterYear;
}
}