diff --git a/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/annotation/Field.java b/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/annotation/Field.java index 2ec00e1..8dd5a5e 100644 --- a/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/annotation/Field.java +++ b/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/annotation/Field.java @@ -56,6 +56,12 @@ */ char paddingChar() default ' '; + /** + * The character to pad with if data is null + * @return the null character + */ + char nullChar() default ' '; + Class formatter() default ByTypeFormatter.class; } diff --git a/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/format/FormatInstructions.java b/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/format/FormatInstructions.java index a04ce2e..6473e8d 100644 --- a/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/format/FormatInstructions.java +++ b/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/format/FormatInstructions.java @@ -32,15 +32,28 @@ public class FormatInstructions { private int length; private Align alignment; private char paddingChar; + private char nullChar; private FixedFormatPatternData fixedFormatPatternData; private FixedFormatBooleanData fixedFormatBooleanData; private FixedFormatNumberData fixedFormatNumberData; private FixedFormatDecimalData fixedFormatDecimalData; - public FormatInstructions(int length, Align alignment, char paddingChar, FixedFormatPatternData fixedFormatPatternData, FixedFormatBooleanData fixedFormatBooleanData, FixedFormatNumberData fixedFormatNumberData, FixedFormatDecimalData fixedFormatDecimalData) { + public FormatInstructions(int length, Align alignment, char paddingChar, FixedFormatPatternData fixedFormatPatternData, FixedFormatBooleanData fixedFormatBooleanData, FixedFormatNumberData fixedFormatNumberData, FixedFormatDecimalData fixedFormatDecimalData) { this.length = length; this.alignment = alignment; this.paddingChar = paddingChar; + this.nullChar = paddingChar; + this.fixedFormatPatternData = fixedFormatPatternData; + this.fixedFormatBooleanData = fixedFormatBooleanData; + this.fixedFormatNumberData = fixedFormatNumberData; + this.fixedFormatDecimalData = fixedFormatDecimalData; + } + + public FormatInstructions(int length, Align alignment, char paddingChar, char nullChar, FixedFormatPatternData fixedFormatPatternData, FixedFormatBooleanData fixedFormatBooleanData, FixedFormatNumberData fixedFormatNumberData, FixedFormatDecimalData fixedFormatDecimalData) { + this.length = length; + this.alignment = alignment; + this.paddingChar = paddingChar; + this.nullChar = nullChar; this.fixedFormatPatternData = fixedFormatPatternData; this.fixedFormatBooleanData = fixedFormatBooleanData; this.fixedFormatNumberData = fixedFormatNumberData; @@ -58,7 +71,11 @@ public Align getAlignment() { public char getPaddingChar() { return paddingChar; } - + + public char getNullChar() { + return nullChar; + } + public FixedFormatPatternData getFixedFormatPatternData() { return fixedFormatPatternData; } @@ -79,7 +96,8 @@ public String toString() { return "FormatInstructions{" + "length=" + length + ", alignment=" + alignment + - ", paddingChar='" + paddingChar + "'" + + ", paddingChar='" + paddingChar + "'" + + ", nullChar='" + nullChar + "'" + ", fixedFormatPatternData=" + fixedFormatPatternData + ", fixedFormatBooleanData=" + fixedFormatBooleanData + ", fixedFormatNumberData=" + fixedFormatNumberData + diff --git a/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl.java b/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl.java index abeaf1d..72ac9e2 100644 --- a/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl.java +++ b/fixedformat4j/src/main/java/com/ancientprogramming/fixedformat4j/format/impl/FixedFormatManagerImpl.java @@ -239,7 +239,23 @@ protected Object readDataAccordingFieldAnnotation(Class clazz, String dat loadedData = load(datatype, dataToParse); } else { try { - loadedData = formatter.parse(dataToParse, formatdata); + boolean isNull = false; + final char nullChar = formatdata.getNullChar(); + final char paddChar = formatdata.getPaddingChar(); + if (nullChar != paddChar) { + isNull = true; + for (int i = 0; i < dataToParse.length(); i++) { + if (nullChar != dataToParse.charAt(i)) { + isNull = false; + break; + } + } + } + if (isNull) { + loadedData = null; + } else { + loadedData = formatter.parse(dataToParse, formatdata); + } } catch (RuntimeException e) { throw new ParseException(data, dataToParse, clazz, method, context, formatdata, e); } @@ -279,7 +295,11 @@ private String exportDataAccordingFieldAnnotation(T fixedFormatRecord, Metho if (valueObject != null && valueObject.getClass().getAnnotation(Record.class) != null) { result = export(valueObject); } else { - result = formatter.format(valueObject, formatdata); + if (valueObject != null) { + result = formatter.format(valueObject, formatdata); + } else { + result = StringUtils.leftPad("", formatdata.getLength(), formatdata.getNullChar()); + } } if (LOG.isDebugEnabled()) { LOG.debug(format("exported %s ", result)); @@ -315,7 +335,7 @@ private FormatInstructions getFormatInstructions(Method method, Field fieldAnno) FixedFormatBooleanData booleanData = getFixedFormatBooleanData(method.getAnnotation(FixedFormatBoolean.class)); FixedFormatNumberData numberData = getFixedFormatNumberData(method.getAnnotation(FixedFormatNumber.class)); FixedFormatDecimalData decimalData = getFixedFormatDecimalData(method.getAnnotation(FixedFormatDecimal.class)); - return new FormatInstructions(fieldAnno.length(), fieldAnno.align(), fieldAnno.paddingChar(), patternData, booleanData, numberData, decimalData); + return new FormatInstructions(fieldAnno.length(), fieldAnno.align(), fieldAnno.paddingChar(), fieldAnno.nullChar(), patternData, booleanData, numberData, decimalData); } private FixedFormatPatternData getFixedFormatPatternData(FixedFormatPattern annotation) { diff --git a/fixedformat4j/src/test/java/com/ancientprogramming/fixedformat4j/format/impl/MyNullableRecord.java b/fixedformat4j/src/test/java/com/ancientprogramming/fixedformat4j/format/impl/MyNullableRecord.java new file mode 100644 index 0000000..1c22cd6 --- /dev/null +++ b/fixedformat4j/src/test/java/com/ancientprogramming/fixedformat4j/format/impl/MyNullableRecord.java @@ -0,0 +1,166 @@ +/* + * Copyright 2004 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ancientprogramming.fixedformat4j.format.impl; + +import com.ancientprogramming.fixedformat4j.annotation.*; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * A record used in testcases + * + * @author Jacob von Eyben - http://www.ancientprogramming.com + * @since 1.0.0 + */ +@Record +public class MyNullableRecord { + + private String stringData; + private Integer integerData; + private Date dateData; + private Character charData; + private Boolean booleanData; + private Long longData; + private Double doubleData; + private Float floatData; + private BigDecimal bigDecimalData; + private float simpleFloatData; + + + @Field(offset = 1, length = 10, align = Align.RIGHT, paddingChar = '*', nullChar=' ') + public String getStringData() { + return stringData; + } + + public void setStringData(String stringData) { + this.stringData = stringData; + } + + @Field(offset = 11, length = 5, align = Align.RIGHT, paddingChar = '0', nullChar=' ') + public Integer getIntegerData() { + return integerData; + } + + public void setIntegerData(Integer integerData) { + this.integerData = integerData; + } + + @Field(offset = 16, length = 8) + public Date getDateData() { + return dateData; + } + + public void setDateData(Date dateData) { + this.dateData = dateData; + } + + @Field(offset = 24, length = 1) + public Character getCharData() { + return charData; + } + + public void setCharData(Character charData) { + this.charData = charData; + } + + @Field(offset = 25, length = 1) + public Boolean isBooleanData() { + return booleanData; + } + + public void setBooleanData(Boolean booleanData) { + this.booleanData = booleanData; + } + + @Field(offset = 26, length = 4, align = Align.RIGHT, paddingChar = '0', nullChar=' ') + public Long getLongData() { + return longData; + } + + public void setLongData(Long longData) { + this.longData = longData; + } + + @Field(offset = 30, length = 10, align = Align.RIGHT, paddingChar = '0', nullChar=' ') + public Double getDoubleData() { + return doubleData; + } + + public void setDoubleData(Double doubleData) { + this.doubleData = doubleData; + } + + @Field(offset = 40, length = 10, align = Align.RIGHT, paddingChar = '0', nullChar=' ') + public Float getFloatData() { + return floatData; + } + + public void setFloatData(Float floatData) { + this.floatData = floatData; + } + + @Field(offset = 50, length = 10, align = Align.RIGHT, paddingChar = '0', nullChar=' ') + @FixedFormatDecimal(decimals = 4, decimalDelimiter = ' ', useDecimalDelimiter = true) + @FixedFormatNumber(sign = Sign.PREPEND) + public BigDecimal getBigDecimalData() { + return bigDecimalData; + } + + public void setBigDecimalData(BigDecimal bigDecimalData) { + this.bigDecimalData = bigDecimalData; + } + + @Field(offset = 60, length = 10, align = Align.RIGHT, paddingChar = '0', nullChar=' ') + public float getSimpleFloatData() { + return simpleFloatData; + } + + public void setSimpleFloatData(float simpleFloatData) { + this.simpleFloatData = simpleFloatData; + } + + + @Record + static class MyStaticNestedClass { + + private String stringData; + + @Field(offset = 1, length = 10) + public String getStringData() { + return stringData; + } + + public void setStringData(String stringData) { + this.stringData = stringData; + } + } + + @Record + class MyInnerClass { + + private String stringData; + + @Field(offset = 1, length = 10) + public String getStringData() { + return stringData; + } + + public void setStringData(String stringData) { + this.stringData = stringData; + } + } +} \ No newline at end of file diff --git a/fixedformat4j/src/test/java/com/ancientprogramming/fixedformat4j/format/impl/TestNullableFixedFormatManagerImpl.java b/fixedformat4j/src/test/java/com/ancientprogramming/fixedformat4j/format/impl/TestNullableFixedFormatManagerImpl.java new file mode 100644 index 0000000..49038d2 --- /dev/null +++ b/fixedformat4j/src/test/java/com/ancientprogramming/fixedformat4j/format/impl/TestNullableFixedFormatManagerImpl.java @@ -0,0 +1,108 @@ +/* + * Copyright 2004 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ancientprogramming.fixedformat4j.format.impl; + +import java.math.BigDecimal; +import java.util.Calendar; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.ancientprogramming.fixedformat4j.format.FixedFormatManager; + +/** + * @author Jacob von Eyben - http://www.ancientprogramming.com + * @since 1.0.0 + */ +public class TestNullableFixedFormatManagerImpl extends TestCase { + private static final Log LOG = LogFactory.getLog(TestNullableFixedFormatManagerImpl.class); + + + public static final String MY_NULLABLE_RECORD_DATA = " 20080514CT001100000010350000002056-0012 01200000002056"; + public static final String MY_NONNULL_RECORD_DATA = "**********0000020080514CT001100000010350000002056-0012 01200000002056"; + + FixedFormatManager manager = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + manager = new FixedFormatManagerImpl(); + } + + public void testLoadNullableRecord() { + MyNullableRecord loadedRecord = manager.load(MyNullableRecord.class, MY_NULLABLE_RECORD_DATA); + Assert.assertNotNull(loadedRecord); + Assert.assertEquals(null, loadedRecord.getStringData()); + Assert.assertTrue(loadedRecord.isBooleanData()); + } + + public void testLoadNonNullRecord() { + MyNullableRecord loadedRecord = manager.load(MyNullableRecord.class, MY_NONNULL_RECORD_DATA); + Assert.assertNotNull(loadedRecord); + Assert.assertEquals("", loadedRecord.getStringData()); + Assert.assertTrue(loadedRecord.isBooleanData()); + } + + + public void testExportNullableRecordObject() { + MyNullableRecord myRecord = createMyNullableRecord(); + Assert.assertEquals(MY_NULLABLE_RECORD_DATA,manager.export(myRecord)); + myRecord = createMyNonNullRecord(); + Assert.assertEquals(MY_NONNULL_RECORD_DATA, manager.export(myRecord)); + } + + private MyNullableRecord createMyNullableRecord() { + Calendar someDay = Calendar.getInstance(); + someDay.set(2008, 4, 14, 0, 0, 0); + someDay.set(Calendar.MILLISECOND, 0); + + MyNullableRecord myRecord = new MyNullableRecord(); + myRecord.setBooleanData(true); + myRecord.setCharData('C'); + myRecord.setDateData(someDay.getTime()); + myRecord.setDoubleData(10.35); + myRecord.setFloatData(20.56F); + myRecord.setLongData(11L); + myRecord.setIntegerData(null); + myRecord.setStringData(null); + myRecord.setBigDecimalData(new BigDecimal(-12.012)); + myRecord.setSimpleFloatData(20.56F); + return myRecord; + } + + private MyNullableRecord createMyNonNullRecord() { + Calendar someDay = Calendar.getInstance(); + someDay.set(2008, 4, 14, 0, 0, 0); + someDay.set(Calendar.MILLISECOND, 0); + + MyNullableRecord myRecord = new MyNullableRecord(); + myRecord.setBooleanData(true); + myRecord.setCharData('C'); + myRecord.setDateData(someDay.getTime()); + myRecord.setDoubleData(10.35); + myRecord.setFloatData(20.56F); + myRecord.setLongData(11L); + myRecord.setIntegerData(0); + myRecord.setStringData(""); + myRecord.setBigDecimalData(new BigDecimal(-12.012)); + myRecord.setSimpleFloatData(20.56F); + return myRecord; + } + +}