001/* 002 * JRecordBind, fixed-length file (un)marshaller 003 * Copyright 2019, Federico Fissore, and individual contributors. See 004 * AUTHORS.txt in the distribution for a full listing of individual 005 * contributors. 006 * 007 * This is free software; you can redistribute it and/or modify it 008 * under the terms of the GNU Lesser General Public License as 009 * published by the Free Software Foundation; either version 2.1 of 010 * the License, or (at your option) any later version. 011 * 012 * This software is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this software; if not, write to the Free 019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 021 */ 022 023package org.fissore.jrecordbind; 024 025import org.fissore.jrecordbind.RecordDefinition.Property; 026 027import java.util.HashMap; 028import java.util.Iterator; 029import java.util.Map; 030import java.util.logging.Level; 031import java.util.logging.Logger; 032import java.util.regex.Pattern; 033 034/** 035 * Generates the regular expression the matches the {@link RecordDefinition} 036 */ 037public class RegexGenerator { 038 039 private final Logger log = Logger.getLogger(RegexGenerator.class.getName()); 040 041 private final Map<RecordDefinition, Pattern> deepPatterns; 042 private final Map<RecordDefinition, Pattern> localPatterns; 043 044 public RegexGenerator() { 045 deepPatterns = new HashMap<>(); 046 localPatterns = new HashMap<>(); 047 } 048 049 private void addFiller(StringBuilder sb, int rowLengthByDefinition, int actualRowLength) { 050 int fillerLength = rowLengthByDefinition - actualRowLength; 051 if (fillerLength > 0) { 052 sb.append("[ ]{").append(fillerLength).append("}"); 053 } 054 } 055 056 public Pattern deepPattern(RecordDefinition definition) { 057 if (!deepPatterns.containsKey(definition)) { 058 StringBuilder sb = new StringBuilder(); 059 deepPattern(definition, sb); 060 deepPatterns.put(definition, Pattern.compile(sb.toString(), Pattern.MULTILINE)); 061 log.log(Level.FINE, () -> "Generated regex: " + deepPatterns.get(definition).toString()); 062 } 063 return deepPatterns.get(definition); 064 } 065 066 private void deepPattern(RecordDefinition definition, StringBuilder sb) { 067 // Open the choice parentheses 068 if (definition.isChoice()) { 069 sb.append("("); 070 } 071 072 localPattern(definition, sb); 073 074 for (Iterator<RecordDefinition> iter = definition.getSubRecords().iterator(); iter.hasNext(); ) { 075 RecordDefinition subRecord = iter.next(); 076 // Open the grouping parentheses 077 sb.append("("); 078 deepPattern(subRecord, sb); 079 // Close the grouping parentheses and open the repeat bracket, and add the 080 // min occurs count 081 sb.append("){").append(subRecord.getMinOccurs()).append(","); 082 083 // It maxOccurs is defined, add it 084 if (subRecord.getMaxOccurs() != -1) { 085 sb.append(subRecord.getMaxOccurs()); 086 } 087 // Close the repeat bracket 088 sb.append("}"); 089 090 // If we have more choice recors, add the choice pipe char 091 if (definition.isChoice() && iter.hasNext()) { 092 sb.append(")|("); 093 } 094 } 095 096 // Close the choice parentheses 097 if (definition.isChoice()) { 098 sb.append(")"); 099 } 100 101 } 102 103 public Pattern localPattern(RecordDefinition definition) { 104 if (!localPatterns.containsKey(definition)) { 105 StringBuilder sb = new StringBuilder(); 106 localPattern(definition, sb); 107 localPatterns.put(definition, Pattern.compile(sb.toString(), Pattern.MULTILINE)); 108 } 109 return localPatterns.get(definition); 110 } 111 112 private void localPattern(RecordDefinition definition, StringBuilder sb) { 113 if (definition.getProperties().isEmpty()) { 114 return; 115 } 116 117 // All records start on a line start 118 sb.append("\\n?^"); 119 120 int currentRow = 0; 121 int actualRowLength = 0; 122 for (Iterator<Property> iter = definition.getProperties().iterator(); iter.hasNext(); ) { 123 Property property = iter.next(); 124 125 // If multiline, add the filler, start new line and step the line counter 126 if (property.getRow() != currentRow) { 127 currentRow = property.getRow(); 128 addFiller(sb, definition.getLength(), actualRowLength); 129 actualRowLength = 0; 130 sb.append("\\n?^"); 131 } 132 actualRowLength += property.getLength(); 133 134 if (property.getFixedValue() != null) { 135 sb.append("(").append(property.getFixedValue()).append(")"); 136 } else if (property.getLength() == 0) { 137 sb.append("(").append(definition.getPropertyPattern()).append("*)"); 138 } else if (property.isEnum()) { 139 sb.append("("); 140 EnumPropertyHelper enumHelper = new EnumPropertyHelper(property); 141 String[] values = enumHelper.getStringValues(); 142 for (int i = 0; i < values.length; i++) { 143 sb.append(values[i]).append("[ ]{").append(property.getLength() - values[i].length()).append("}"); 144 if (i < values.length - 1) { 145 sb.append("|"); 146 } 147 } 148 sb.append(")"); 149 } else { 150 sb.append("([\\w\\W]{").append(property.getLength()).append("})"); 151 } 152 153 if (iter.hasNext() && definition.isDelimited()) { 154 sb.append(Pattern.quote(definition.getPropertyDelimiter())); 155 actualRowLength++; 156 } 157 } 158 159 addFiller(sb, definition.getLength(), actualRowLength); 160 } 161}