/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.data.dbf;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Calendar;
import java.util.Date;
import org.geotoolkit.util.XInteger;
/**
* Class for holding the information assicated with a record.
*
* @author Johann Sorel (Geomatys)
*/
abstract class DbaseField {
private static final Number NULL_NUMBER = new Integer(0);
private static final String NULL_STRING = "";
private static final Date NULL_DATE = new Date();
public static DbaseField create(final String fieldName, final char fieldType, final int fieldDataAddress,
final int fieldLength, final int decimalCount, final Class clazz) throws IOException{
switch (fieldType) {
// (L)logical (T,t,F,f,Y,y,N,n)
case 'l':
case 'L':
return new BooleanField(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
// (C)character (String)
case 'c':
case 'C':
return new CharField(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
// (D)date (Date)
case 'd':
case 'D':
return new DateField(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
// (N)Numeric (Integer/Long)
case 'n':
case 'N':
if(clazz == Integer.class){
return new IntegerField(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
}else if(clazz == Long.class){
return new LongField(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
}
case 'f':
case 'F': // floating point number
return new FloatingField(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
default:
throw new IOException("Invalid field type : " + fieldType);
}
}
/**
* Create a copy of hte DbaseField with a different adress
*/
public static DbaseField create(final DbaseField field, final int fieldDataAddress){
if(field instanceof BooleanField){
return new BooleanField(field.fieldName, field.fieldType, fieldDataAddress,
field.fieldLength, field.decimalCount, field.clazz);
}else if(field instanceof CharField){
return new CharField(field.fieldName, field.fieldType, fieldDataAddress,
field.fieldLength, field.decimalCount, field.clazz);
}else if(field instanceof DateField){
return new DateField(field.fieldName, field.fieldType, fieldDataAddress,
field.fieldLength, field.decimalCount, field.clazz);
}else if(field instanceof IntegerField){
return new IntegerField(field.fieldName, field.fieldType, fieldDataAddress,
field.fieldLength, field.decimalCount, field.clazz);
}else if(field instanceof LongField){
return new LongField(field.fieldName, field.fieldType, fieldDataAddress,
field.fieldLength, field.decimalCount, field.clazz);
}else if(field instanceof FloatingField){
return new FloatingField(field.fieldName, field.fieldType, fieldDataAddress,
field.fieldLength, field.decimalCount, field.clazz);
}else{
throw new IllegalArgumentException("DBaseField unknowned : " + field);
}
}
//todo should be set final
// Field Name
public final String fieldName;
// Field Type (C N L D or M)
public final char fieldType;
// Field Data Address offset from the start of the record.
public final int fieldDataAddress;
// Length of the data in bytes
public final int fieldLength;
// Field decimal count in Binary, indicating where the decimal is
public final int decimalCount;
// Class
public final Class clazz;
private DbaseField(final String fieldName, final char fieldType, final int fieldDataAddress,
final int fieldLength, final int decimalCount, final Class clazz) {
this.fieldName = fieldName;
this.fieldType = fieldType;
this.fieldDataAddress = fieldDataAddress;
this.fieldLength = fieldLength;
this.decimalCount = decimalCount;
this.clazz = clazz;
}
protected CharSequence extractNumberString(final CharBuffer charBuffer2,
int fieldOffset) {
final int fieldLen = fieldOffset+fieldLength;
while(fieldOffset<fieldLen && charBuffer2.charAt(fieldOffset) <= ' ') fieldOffset++;
return charBuffer2.subSequence(fieldOffset, fieldLen);
}
public abstract Object read(CharBuffer charBuffer) throws IOException;
public abstract String string(Object obj, DbaseFieldFormatter formatter) throws IOException;
private static final class BooleanField extends DbaseField{
public BooleanField(final String fieldName, final char fieldType, final int fieldDataAddress,
final int fieldLength, final int decimalCount, final Class clazz) {
super(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
}
@Override
public Object read(final CharBuffer charBuffer) throws IOException {
switch (charBuffer.charAt(0)) {
case 't':
case 'T':
case 'Y':
case 'y':
return Boolean.TRUE;
case 'f':
case 'F':
case 'N':
case 'n':
return Boolean.FALSE;
default:
throw new IOException("Unknown logical value : '"
+ charBuffer.charAt(0) + "'");
}
}
@Override
public String string(final Object obj, final DbaseFieldFormatter formatter) throws IOException {
return (obj == null ? "F" : obj == Boolean.TRUE ? "T" : "F");
}
}
private static final class CharField extends DbaseField{
public CharField(final String fieldName, final char fieldType, final int fieldDataAddress,
final int fieldLength, final int decimalCount, final Class clazz) {
super(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
}
@Override
public Object read(final CharBuffer charBuffer) throws IOException {
// oh, this seems like a lot of work to parse strings...but,
// For some reason if zero characters ( (int) char == 0 ) are allowed
// in these strings, they do not compare correctly later on down
// the line....
int start = 0;
int end = fieldLength - 1;
charBuffer.limit(charBuffer.capacity());
// trim off whitespace and 'zero' chars
while (start < end) {
char c = charBuffer.get(start);
if (c == 0 || Character.isWhitespace(c)) {
start++;
} else
break;
}
while (end >= start) {
char c = charBuffer.get(end);
if (c == 0 || Character.isWhitespace(c)) {
end--;
} else
break;
}
// set up the new indexes for start and end
charBuffer.position(start).limit(end + 1);
String s = charBuffer.toString();
charBuffer.limit(charBuffer.capacity());
return s;
}
@Override
public String string(final Object obj, final DbaseFieldFormatter formatter) throws IOException {
return formatter.getFieldString(fieldLength, obj == null ? NULL_STRING
: obj.toString());
}
}
private static final class DateField extends DbaseField{
public DateField(final String fieldName, final char fieldType, final int fieldDataAddress,
final int fieldLength, final int decimalCount, final Class clazz) {
super(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
}
@Override
public Object read(final CharBuffer charBuffer) throws IOException {
try {
String tempString = charBuffer.subSequence(0,4).toString();
final int tempYear = Integer.parseInt(tempString);
tempString = charBuffer.subSequence(4,6).toString();
final int tempMonth = Integer.parseInt(tempString) - 1;
tempString = charBuffer.subSequence(6,8).toString();
final int tempDay = Integer.parseInt(tempString);
final Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(Calendar.YEAR, tempYear);
cal.set(Calendar.MONTH, tempMonth);
cal.set(Calendar.DAY_OF_MONTH, tempDay);
return cal.getTime();
} catch (NumberFormatException nfe) {
// todo: use progresslistener, this isn't a grave error.
return null;
}
}
@Override
public String string(final Object obj, final DbaseFieldFormatter formatter) throws IOException {
return formatter.getFieldString((Date) (obj == null ? NULL_DATE : obj));
}
}
private static final class IntegerField extends DbaseField{
private static final Long ZERO = 0l;
public IntegerField(final String fieldName, final char fieldType, final int fieldDataAddress,
final int fieldLength, final int decimalCount, final Class clazz) {
super(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
}
@Override
public Object read(final CharBuffer charBuffer) throws IOException {
try {
final CharSequence number = extractNumberString(charBuffer,0);
return XInteger.parseIntSigned(number, 0, number.length());
// else will fall through to the floating point number
} catch (NumberFormatException e) {
// Lets try parsing a long instead...
try {
return Long.valueOf(extractNumberString(charBuffer, 0).toString());
} catch (NumberFormatException e2) {
return ZERO;
}
}
}
@Override
public String string(final Object obj, final DbaseFieldFormatter formatter) throws IOException {
return formatter.getFieldString(fieldLength, 0,(Number) (obj == null ? NULL_NUMBER : obj));
}
}
private static final class LongField extends DbaseField{
private static final Long ZERO = 0l;
public LongField(final String fieldName, final char fieldType, final int fieldDataAddress,
final int fieldLength, final int decimalCount, final Class clazz) {
super(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
}
@Override
public Object read(final CharBuffer charBuffer) throws IOException {
try {
final CharSequence number = extractNumberString(charBuffer,0);
return Long.valueOf(number.toString());
// else will fall through to the floating point number
} catch (NumberFormatException e) {
return ZERO;
}
}
@Override
public String string(final Object obj, final DbaseFieldFormatter formatter) throws IOException {
return formatter.getFieldString(fieldLength, 0,(Number) (obj == null ? NULL_NUMBER : obj));
}
}
private static final class FloatingField extends DbaseField{
private static final Double ZERO = 0d;
public FloatingField(final String fieldName, final char fieldType, final int fieldDataAddress,
final int fieldLength, final int decimalCount, final Class clazz) {
super(fieldName, fieldType, fieldDataAddress, fieldLength, decimalCount,clazz);
}
@Override
public Object read(final CharBuffer charBuffer) throws IOException {
try {
return Double.valueOf(extractNumberString(charBuffer, 0).toString());
} catch (NumberFormatException e) {
// todo: use progresslistener, this isn't a grave error,
// though it does indicate something is wrong
// okay, now whatever we got was truly undigestable. Lets go
// with a zero Double.
return ZERO;
}
}
@Override
public String string(final Object obj, final DbaseFieldFormatter formatter) throws IOException {
return formatter.getFieldString(fieldLength, decimalCount,
(Number) (obj == null ? NULL_NUMBER : obj));
}
}
}