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}