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;
026import org.fissore.jrecordbind.padders.SpaceRightPadder;
027
028import java.io.IOException;
029import java.io.Writer;
030import java.util.*;
031
032/**
033 * Transforms beans to text according to the given record definition
034 */
035public class Marshaller<E> extends AbstractUnMarshaller {
036
037  /**
038   * Creates a new marshaller, with the specified record definition
039   *
040   * @param definition the record definition
041   * @see SpaceRightPadder
042   */
043  public Marshaller(RecordDefinition definition) {
044    this(definition, new HashMap<>(), new HashMap<>());
045  }
046
047  /**
048   * Creates a new marshaller, with the specified record definition and user provided instances of converters and padders.
049   *
050   * @param definition the record definition
051   * @param converters user provided instances of converters
052   * @param padders    user provided instances of padders
053   */
054  public Marshaller(RecordDefinition definition, Map<String, Converter> converters, Map<String, Padder> padders) {
055    super(definition, converters, padders);
056  }
057
058  private void fillWithBlanks(StringBuilder sb, int definitionLength, int length) {
059    int fillerLength = definitionLength - length;
060    while (fillerLength-- > 0) {
061      sb.append(" ");
062    }
063  }
064
065  private Object ensureCorrectLength(int length, String value) {
066    if (length > 0 && value.length() > length) {
067      return value.substring(0, length);
068    }
069    return value;
070  }
071
072  /**
073   * Marshalls a bean to a writer
074   *
075   * @param record the bean to marshal
076   * @param writer the target writer
077   */
078  public void marshall(E record, Writer writer) {
079    marshall(record, definition, writer);
080  }
081
082  private void marshall(Object record, RecordDefinition currentDefinition, Writer writer) {
083    StringBuilder sb = new StringBuilder(currentDefinition.getLength());
084    int currentRow = 0;
085    int length = 0;
086    boolean propertyFound = false;
087    for (Iterator<Property> iter = currentDefinition.getProperties().iterator(); iter.hasNext(); ) {
088      propertyFound = true;
089      Property property = iter.next();
090      if (property.getRow() != currentRow) {
091        currentRow = property.getRow();
092        fillWithBlanks(sb, currentDefinition.getLength(), length);
093        length = 0;
094        sb.append(definition.getLineSeparator());
095      }
096      String propertyValue = getPropertyValue(record, currentDefinition, property);
097      sb.append(ensureCorrectLength(property.getLength(), propertyValue));
098      length += property.getLength();
099      if (iter.hasNext()) {
100        sb.append(currentDefinition.getPropertyDelimiter());
101        length += currentDefinition.getPropertyDelimiter().length();
102      }
103    }
104
105    if (propertyFound) {
106      fillWithBlanks(sb, currentDefinition.getLength(), length);
107
108      sb.append(definition.getLineSeparator());
109    }
110
111    try {
112      writer.append(sb.toString());
113    } catch (IOException e) {
114      throw new RuntimeException(e);
115    }
116
117    if (currentDefinition.isChoice()) {
118      Optional<RecordDefinition> choiceSubDefinition = currentDefinition.getSubRecords().stream()
119        .filter(subDefinition -> {
120          Object subRecord = propertyUtils.getProperty(record, subDefinition.getSetterName());
121          return subRecord != null && subRecord.getClass().getName().equals(subDefinition.getClassName());
122        })
123        .findFirst();
124
125      if (choiceSubDefinition.isPresent()) {
126        RecordDefinition subDefinition = choiceSubDefinition.get();
127        Object subRecord = propertyUtils.getProperty(record, subDefinition.getSetterName());
128
129        marshallSubRecord(subRecord, subDefinition, writer);
130      }
131    } else {
132      for (RecordDefinition subDefinition : currentDefinition.getSubRecords()) {
133        Object subRecord = propertyUtils.getProperty(record, subDefinition.getSetterName());
134        if (subRecord == null && !currentDefinition.isChoice()) {
135          throw new NullPointerException("Missing object from " + subDefinition.getSetterName());
136        }
137        marshallSubRecord(subRecord, subDefinition, writer);
138      }
139    }
140  }
141
142  @SuppressWarnings("unchecked")
143  private void marshallSubRecord(Object subRecord, RecordDefinition subDefinition, Writer writer) {
144    if (subRecord != null) {
145      if (subRecord instanceof Collection) {
146        Collection<Object> subRecords = (Collection<Object>) subRecord;
147        for (Object o : subRecords) {
148          marshall(o, subDefinition, writer);
149        }
150      } else {
151        marshall(subRecord, subDefinition, writer);
152      }
153    }
154  }
155
156  private String getPropertyValue(Object record, RecordDefinition currentDefinition, Property property) {
157    if (property.getFixedValue() != null) {
158      return property.getFixedValue();
159    }
160
161    Object propertyValue = propertyUtils.getProperty(record, property.getName());
162    String convertedPropertyValue = getConverter(property).toString(propertyValue);
163    String paddedPropertyValue = getPadder(currentDefinition, property).pad(convertedPropertyValue, property.getLength());
164
165    return paddedPropertyValue;
166  }
167
168  /**
169   * Marshalls a collection of beans to a writer
170   *
171   * @param records the beans to marshall
172   * @param writer  the target writer
173   */
174  public void marshallAll(Collection<E> records, Writer writer) {
175    for (E record : records) {
176      marshall(record, definition, writer);
177    }
178  }
179}