package gov.nysenate.openleg.processor.law; import gov.nysenate.openleg.model.law.LawDocumentType; import gov.nysenate.openleg.model.law.LawTree; import gov.nysenate.openleg.model.law.LawTreeNode; import gov.nysenate.openleg.model.law.LawVersionId; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.regex.Matcher; /** * Some laws have inconsistent document ids that result in incorrect hierarchies when using the * {@link IdBasedLawBuilder}. This implementation can be initialized with an expected ordering of * the document types so that the sub documents can be paired correctly with their parent doc. */ public class HintBasedLawBuilder extends IdBasedLawBuilder implements LawBuilder { private static final Logger logger = LoggerFactory.getLogger(HintBasedLawBuilder.class); /** Ordered list of law document types used to constrain the hierarchy to certain nesting rules. */ private LinkedList<LawDocumentType> expectedOrder; private Map<LawDocumentType, LawTreeNode> lastParentNodeOfType = new HashMap<>(); /** --- Constructors --- */ public HintBasedLawBuilder(LawVersionId lawVersionId, List<LawDocumentType> expectedOrder) { super(lawVersionId); this.expectedOrder = new LinkedList<>(expectedOrder); } public HintBasedLawBuilder(LawVersionId lawVersionId, LawTree previousTree, List<LawDocumentType> expectedOrder) { super(lawVersionId, previousTree); this.expectedOrder = new LinkedList<>(expectedOrder); } /** --- Overrides --- */ @Override protected String determineHierarchy(LawBlock block) { Stack<LawTreeNode> backup = (Stack<LawTreeNode>) parentNodes.clone(); String locationId = super.determineHierarchy(block); if (currParent().isRootNode()) { // Determine doc type Matcher locMatcher = locationPattern.matcher(locationId); if (locMatcher.matches()) { LawDocumentType docType = lawLevelCodes.get(locMatcher.group(1)); if (!docType.equals(expectedOrder.getFirst())) { // Possible mismatch logger.info("Possible mismatch for {}", locationId); for (int i = 1; i < expectedOrder.size(); i++) { if (expectedOrder.get(i).equals(docType)) { if (i != 0) { LawDocumentType expectedType = expectedOrder.get(i - 1); if (lastParentNodeOfType.containsKey(expectedType)) { LawTreeNode expectedParent = lastParentNodeOfType.get(expectedType); parentNodes = backup; parentNodes.push(expectedParent); logger.info("Guessing actual parent is {}", expectedParent); } } break; } } } } } return locationId; } @Override protected void addChildNode(LawTreeNode node) { super.addChildNode(node); if (!node.isRootNode() && !node.getDocType().equals(LawDocumentType.SECTION)) { lastParentNodeOfType.put(node.getDocType(), node); } } }