package gov.nysenate.openleg.model.sobi; import gov.nysenate.openleg.model.bill.BillId; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; /** * The SobiBlock class represents a set of SOBI lines sharing the same header: * <p> * <pre> * 2013A03006D3Amends various provisions of law relating to implementing the health and mental hygiene budget for * 2013A03006D3the 2013-2014 state fiscal year. * </pre> * All of the different header components are parsed out and data is aggregated: * <p> * <ul> * <li>year: 2013</li> * <li>printNo: "A03006"</li> * <li>amendment: "D"</li> * <li>type: '3'</li> * <li>data: Amends various provisions of law relating to implementing the health and mental hygiene budget for * the 2013-2014 state fiscal year.</li> * </ul> * <p> * Certain block types and block content should always be single lined. Specifically type 1, 2, and 5 blocks as well * as any blocks having "DELETE" as data. A block should always be checked for isMultiline before being extended. * * @author GraylinKim * */ public class SobiBlock { /** A list of sobi line types that are *single* line blocks. All other block types are multi-line. */ public static final List<SobiLineType> oneLineBlocks = Arrays.asList(SobiLineType.BILL_INFO, SobiLineType.LAW_SECTION, SobiLineType.SAME_AS); /** A pattern to verify that a string is in the SOBI line format. */ public static final Pattern blockPattern = Pattern.compile("^[0-9]{4}[A-Z][0-9]{5}[ A-Z][1-9A-Z]"); /** The number of characters in the data segment of the block */ public static final int blockDataLength = 98; /** The file name of the fragment that generated this block. */ private String fragmentFileName; /** The type of SobiFragment that generated this block. */ private SobiFragmentType fragmentType; /** The line number that the block starts at. Defaults to zero when using the basic constructor. */ private Integer startLineNo = 0; /** The line number that the block ends at. Defaults to zero when using the basic constructor. */ private Integer endLineNo = 0; /** The full line header from the line used to construct the Block. */ private String header = ""; /** The portion of the header containing just the bill designator, i.e not including line type. */ private String billHeader = ""; /** The bill identifier. */ private BillId billId; /** The sobi line type of the block. Determines how the data is interpreted. */ private SobiLineType type; /** An internal buffer used to accumulate block data over several lines. */ private StringBuffer dataBuffer = new StringBuffer(); /** True if the block should be extended by multiple lines. This is generally determined by block type * but blocks whose data is DELETE should be treated as single line blocks regardless of type. */ private final boolean multiline; /** --- Constructors --- */ /** * Construct a new block with without location information from a valid SobiFile line. The line is * assumed to be valid sobi file and is NOT checked for performance reasons. */ public SobiBlock(String line) { this.setBillHeader(line.substring(0, 11)); this.setBillId(line.substring(4,10), line.substring(10,11), Integer.parseInt(line.substring(0,4))); this.setType(SobiLineType.valueOfCode(line.charAt(11))); this.setHeader(line.substring(0,12)); this.setData(line.substring(12)); this.multiline = !oneLineBlocks.contains(this.getType()) && !this.getData().trim().equals("DELETE"); } /** * Construct a new block with location information from a valid SOBI line. Holds references to * the source file and line number the block was initialized from. The line is assumed to be * valid SOBI file and is NOT checked for performance reasons. */ public SobiBlock(String fragmentFileName, SobiFragmentType type, int startLineNo, String line) { this(line); this.fragmentFileName = fragmentFileName; this.fragmentType = type; this.setStartLineNo(startLineNo); } /** --- Methods --- */ /** * Extends the block data with the data from the new line. Separates new lines with a '\n' character so * that line breaks information is available to downstream parsers. * * @throws RuntimeException - when attempting to extend a block that shouldn't be extended. Use isMultiline * to check before extending. */ public void extend(String line) { if (!this.isMultiline()) throw new RuntimeException("Only multi-line blocks may be extended"); this.dataBuffer.append("\n"+line.substring(12)); } /** * Blocks are considered equal if their header and data (trimmed of all excess whitespace) are * identical in content (case-sensitive). */ public boolean equals(Object obj) { return obj!= null && obj instanceof SobiBlock && ((SobiBlock)obj).getHeader().equals(this.getHeader()) && ((SobiBlock)obj).getData().trim().equals(this.getData().trim()); } public String toString() { return this.getLocation()+":"+this.getHeader(); } /** --- Functional Getters/Setters */ /** * Returns a representation of the location of the block: fileName:lineNumber. */ public String getLocation() { return (this.fragmentFileName + ":" + this.getStartLineNo() + "-" + this.getEndLineNo()); } /** * Gets the string representation of the block's data. */ public String getData() { ensureDataLength(); return dataBuffer.toString(); } /** * Replaces the block data with the input string. */ public void setData(String data) { this.dataBuffer = new StringBuffer(data); } /** * Sets the BillId for this block. * * @throws NumberFormatException on malformed print numbers */ public void setBillId(String printNo, String version, int session) { this.billId = new BillId(printNo, session, version); } /** * Sets the end line number ensuring that it is >= 0. */ public void setEndLineNo(Integer endLineNo) { if (endLineNo < 0) { endLineNo = 0; } this.endLineNo = endLineNo; } /** --- Internal Methods --- */ /** * Ensures that the data segment of the block extends to the full 98 characters by appending spaces as needed * This is only applied to bill info blocks */ private void ensureDataLength() { if (type == SobiLineType.BILL_INFO) { for (int i = 0; i < blockDataLength - dataBuffer.length(); i++) { dataBuffer.append(" "); } } } /** --- Basic Getters/Setters */ public String getFragmentFileName() { return fragmentFileName; } public void setFragmentFileName(String fragmentFileName) { this.fragmentFileName = fragmentFileName; } public SobiFragmentType getFragmentType() { return fragmentType; } public void setFragmentType(SobiFragmentType fragmentType) { this.fragmentType = fragmentType; } public int getStartLineNo() { return startLineNo; } public void setStartLineNo(int startLineNo) { this.startLineNo = startLineNo; } public Integer getEndLineNo() { return endLineNo; } public String getHeader() { return header; } public void setHeader(String header) { this.header = header; } public SobiLineType getType() { return type; } public void setType(SobiLineType type) { this.type = type; } public String getBillHeader() { return billHeader; } public void setBillHeader(String billHeader) { this.billHeader = billHeader; } public BillId getBillId() { return billId; } public void setBillId(BillId billId) { this.billId = billId; } public boolean isMultiline() { return multiline; } }