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.padders.SpaceRightPadder;
026
027import java.util.ArrayList;
028import java.util.List;
029
030/***
031 * The definition of the record bean. This bean rapresentation of
032 * the XML Schema fixed-length file definition (.xsd)
033 */
034public class RecordDefinition {
035
036  /**
037   * A single "element" in the fixed-length file definition
038   */
039  public static class Property {
040
041    private String converter;
042    private String fixedValue;
043    private int length;
044    private final String name;
045    private String padder;
046    private int row;
047    private String type;
048    private final EnumPropertyHelper enumPropertyHelper;
049
050    /**
051     * Creates a new Property
052     *
053     * @param name the name of the property
054     */
055    public Property(String name) {
056      this.name = name;
057      this.enumPropertyHelper = new EnumPropertyHelper(this);
058    }
059
060    /**
061     * The fully qualified class name of the converter used to marshall and
062     * unmarshall this property
063     *
064     * @return a fully qualified class name
065     */
066    public String getConverter() {
067      return converter;
068    }
069
070    /**
071     * The fixed value of this property, usually used to identify different
072     * lines in a single file
073     *
074     * @return the value
075     */
076    public String getFixedValue() {
077      return fixedValue;
078    }
079
080    /**
081     * The length of the property
082     *
083     * @return the length of the property
084     */
085    public int getLength() {
086      return length;
087    }
088
089    /**
090     * The name of the property
091     *
092     * @return the name of the property
093     */
094    public String getName() {
095      return name;
096    }
097
098    /**
099     * The padder used to pad this property value. If missing (<code>null</code>
100     * ) the default padder will be used
101     *
102     * @return a fully qualified class name or <code>null</code> if none specified
103     */
104    public String getPadder() {
105      return padder;
106    }
107
108    /**
109     * The row this property is at. If this is the first property at
110     * row 2, every subsequent property must be at least at row 2. Consistency
111     * is up to the definition writer (developer)
112     *
113     * @return the row
114     */
115    public int getRow() {
116      return row;
117    }
118
119    /**
120     * The class name of the type of this property. Internally supported type
121     * names are not fully qualified (String, Integer...)
122     *
123     * @return the class name of this property type
124     */
125    public String getType() {
126      return type;
127    }
128
129    public void setConverter(String converter) {
130      this.converter = converter;
131    }
132
133    public void setFixedValue(String value) {
134      this.fixedValue = value;
135    }
136
137    public void setLength(int end) {
138      this.length = end;
139    }
140
141    public void setPadder(String padder) {
142      this.padder = padder;
143    }
144
145    public void setRow(int row) {
146      this.row = row;
147    }
148
149    public void setType(String type) {
150      this.type = type;
151    }
152
153    public boolean isEnum() {
154      return enumPropertyHelper.isEnum();
155    }
156
157  }
158
159  private boolean choice;
160  private String className;
161  private String defaultPadder;
162  private int length;
163  private String lineSeparator;
164  private int maxOccurs;
165  private int minOccurs;
166  private final RecordDefinition parent;
167  private String printableLineSeparator;
168  private final List<Property> properties;
169  private String propertyDelimiter;
170  private String propertyPattern;
171  private String setterName;
172  private final List<RecordDefinition> subRecords;
173
174  /**
175   * Creates a new instance, without a parent (aka: the main definition)
176   */
177  public RecordDefinition() {
178    this(null);
179  }
180
181  /**
182   * Creates a new instance, with the given parent definition.
183   *
184   * @param parent the parent record definition
185   */
186  public RecordDefinition(RecordDefinition parent) {
187    this.properties = new ArrayList<>();
188    this.propertyDelimiter = "";
189    this.subRecords = new ArrayList<>();
190    this.parent = parent;
191    this.defaultPadder = SpaceRightPadder.class.getName();
192  }
193
194  /**
195   * The fully qualified class name described by this definition
196   *
197   * @return a fully qualified class name
198   */
199  public String getClassName() {
200    return className;
201  }
202
203  public String getDefaultPadder() {
204    if (hasParent()) {
205      return parent.getDefaultPadder();
206    }
207    return defaultPadder;
208  }
209
210  /**
211   * The length of the fixed-length file
212   *
213   * @return the length
214   */
215  public int getLength() {
216    if (hasParent()) {
217      return parent.getLength();
218    }
219    return length;
220  }
221
222  public String getLineSeparator() {
223    if (hasParent()) {
224      return parent.getLineSeparator();
225    }
226    return lineSeparator;
227  }
228
229  /**
230   * How many times this definition can occur in the fixed-length file? Main
231   * definition can occur 1 time only, subdefinitions can vary
232   *
233   * @return an int
234   */
235  public int getMaxOccurs() {
236    return maxOccurs;
237  }
238
239  /**
240   * How many times this definition must occur in the fixed-length file? Main
241   * definition must occur 1 time, subdefinitions can vary
242   *
243   * @return an int
244   */
245  public int getMinOccurs() {
246    return minOccurs;
247  }
248
249  public RecordDefinition getParent() {
250    return parent;
251  }
252
253  public String getPrintableLineSeparator() {
254    if (hasParent()) {
255      return parent.getPrintableLineSeparator();
256    }
257    return printableLineSeparator;
258  }
259
260  /**
261   * The list of {@link Property properties} contained by this definition
262   *
263   * @return the list of {@link Property properties}
264   */
265  public List<Property> getProperties() {
266    return properties;
267  }
268
269  /**
270   * The delimiter used in the fixed-length file
271   *
272   * @return the delimiter
273   */
274  public String getPropertyDelimiter() {
275    if (hasParent()) {
276      return parent.getPropertyDelimiter();
277    }
278    return propertyDelimiter;
279  }
280
281  /**
282   * Tells if properties of this record are delimited
283   *
284   * @return True if properties of the record are delimited, false if the record is fixed-length
285   */
286  public boolean isDelimited() {
287    return (!"".equals(getPropertyDelimiter()));
288  }
289
290  /**
291   * The name of the property used to set records from this definition in the
292   * parent (container) definition. E.G.: the "name" attribute in "element" like
293   * the following<br>
294   * <code>&lt;xs:element name="child" type="RowChildRecord"/&gt;</code>
295   *
296   * @return a property name
297   */
298  public String getSetterName() {
299    return setterName;
300  }
301
302  /**
303   * The sub definitions contained by this definition (hierarchy based files)
304   *
305   * @return the list of sub definitions
306   */
307  public List<RecordDefinition> getSubRecords() {
308    return subRecords;
309  }
310
311  /**
312   * The regex that matches a property
313   * (i.e. a string that does not contain a delimiter)
314   *
315   * @return the regex that matches a delimited property
316   */
317  public String getPropertyPattern() {
318    if (hasParent()) {
319      return parent.getPropertyPattern();
320    }
321    return propertyPattern;
322  }
323
324  @Override
325  public int hashCode() {
326    return getClassName().hashCode();
327  }
328
329  public boolean hasParent() {
330    return parent != null;
331  }
332
333  public boolean isChoice() {
334    return choice;
335  }
336
337  public void setChoice(boolean choice) {
338    this.choice = choice;
339  }
340
341  public void setClassName(String fullyQualifiedClassName) {
342    this.className = fullyQualifiedClassName;
343  }
344
345  public void setClassName(String className, String packageName) {
346    this.className = packageName + "." + className;
347  }
348
349  public void setDefaultPadder(String padder) {
350    if (padder == null) {
351      return;
352    }
353    this.defaultPadder = padder;
354  }
355
356  public void setLength(int length) {
357    this.length = length;
358  }
359
360  public void setLineSeparator(String separator) {
361    this.lineSeparator = separator;
362    StringBuilder sb = new StringBuilder();
363    for (char c : this.lineSeparator.toCharArray()) {
364      if (!Character.isISOControl(c)) {
365        sb.append(c);
366      }
367    }
368    this.printableLineSeparator = sb.toString();
369  }
370
371  public void setMaxOccurs(int maxOccurs) {
372    this.maxOccurs = maxOccurs;
373  }
374
375  public void setMinOccurs(int minOccurs) {
376    this.minOccurs = minOccurs;
377  }
378
379  public void setPropertyDelimiter(String delimiter) {
380    if (delimiter == null) {
381      return;
382    }
383    this.propertyDelimiter = delimiter;
384    this.propertyPattern = generatePropertyPattern(delimiter);
385  }
386
387  public void setSetterName(String setterName) {
388    this.setterName = setterName;
389  }
390
391  private String generatePropertyPattern(String delimiter) {
392    if (delimiter.equals("")) {
393      return (".");
394    } else if (delimiter.length() == 1) {
395      return ("[^\\Q" + delimiter + "\\E\\n]");
396    } else {
397      return "[^\\Q" + delimiter.substring(0, 1) + "\\E\\n]|(?:\\Q" + delimiter.substring(0, 1) + "\\E(?!\\Q" + delimiter.substring(1) + "\\E))";
398    }
399  }
400
401}