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}