/** * H2GIS is a library that brings spatial support to the H2 Database Engine * <http://www.h2database.com>. H2GIS is developed by CNRS * <http://www.cnrs.fr/>. * * This code is part of the H2GIS project. H2GIS 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 3.0 of the License. * * H2GIS 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 <http://www.gnu.org/licenses/>. * * * For more information, please consult: <http://www.h2gis.org/> * or contact directly: info_at_h2gis.org */ package org.h2gis.functions.io.dbf.internal; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.nio.charset.Charset; import java.text.FieldPosition; import java.text.NumberFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; /** * A DbaseFileReader is used to read a dbase III format file. The general use of * this class is: <CODE><PRE> * DbaseFileHeader header = ... * WritableFileChannel out = new FileOutputStream("thefile.dbf").getChannel(); * DbaseFileWriter w = new DbaseFileWriter(header,out); * while ( moreRecords ) { * w.write( getMyRecord() ); * } * w.close(); * </PRE></CODE> You must supply the <CODE>moreRecords</CODE> and * <CODE>getMyRecord()</CODE> logic... * * @author Ian Schneider * @source $URL: * http://svn.geotools.org/geotools/trunk/gt/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/dbf/DbaseFileWriter.java $ */ public class DbaseFileWriter { private DbaseFileHeader header; private DbaseFileWriter.FieldFormatter formatter; WritableByteChannel channel; private ByteBuffer buffer; private static final Number NULL_NUMBER = Integer.valueOf(0); private static final String NULL_STRING = ""; private Charset charset; /** * Create a DbaseFileWriter using the specified header and writing to the * given channel. * * @param header * The DbaseFileHeader to write. * @param out * The Channel to write to. * @throws java.io.IOException * If errors occur while initializing. */ public DbaseFileWriter(DbaseFileHeader header, WritableByteChannel out) throws IOException { this(header, out, Charset.forName(header.getFileEncoding())); } /** * @return The DbaseFileHeader to write. */ public DbaseFileHeader getHeader() { return header; } /** * Create a DbaseFileWriter using the specified header and writing to the * given channel. * * @param header * The DbaseFileHeader to write. * @param out * The Channel to write to. * @param charset * The charset the dbf is (will be) encoded in * @throws java.io.IOException * If errors occur while initializing. */ public DbaseFileWriter(DbaseFileHeader header, WritableByteChannel out, Charset charset) throws IOException { header.writeHeader(out); this.header = header; this.channel = out; // DBase does not support UTF-8 this.charset = charset == null ? Charset.forName(DbaseFileHeader.DEFAULT_ENCODING) : charset; this.formatter = new DbaseFileWriter.FieldFormatter(this.charset); init(); } private void init() throws IOException { buffer = ByteBuffer.allocateDirect(header.getRecordLength()); } private void write() throws IOException { buffer.position(0); int r = buffer.remaining(); do { r -= channel.write(buffer); } while (r > 0); } /** * Write a single dbase record. * * @param record * The entries to write. * @throws java.io.IOException * If IO error occurs. * @throws DbaseFileException * If the entry doesn't comply to the header. */ public void write(Object[] record) throws IOException, DbaseFileException { if (record.length != header.getNumFields()) { throw new DbaseFileException("Wrong number of fields " + record.length + " expected " + header.getNumFields()); } buffer.position(0); // put the 'not-deleted' marker buffer.put((byte) ' '); for (int i = 0; i < header.getNumFields(); i++) { String fieldString = fieldString(record[i], i); if (header.getFieldLength(i) != fieldString .getBytes(charset.name()).length) { buffer.put(new byte[header.getFieldLength(i)]); } else { buffer.put(fieldString.getBytes(charset.name())); } } write(); } private String fieldString(Object obj, final int col) { String o; final int fieldLen = header.getFieldLength(col); switch (header.getFieldType(col)) { case 'C': case 'M': case 'G': case 'c': o = formatter.getFieldString(fieldLen, obj != null ? obj.toString() : NULL_STRING); break; case 'L': case 'l': o = (obj == null ? "F" : (Boolean)obj ? "T" : "F"); break; case 'N': case 'n': // int? if (header.getFieldDecimalCount(col) == 0) { o = formatter.getFieldString(fieldLen, 0, (obj instanceof Number ? (Number)obj : NULL_NUMBER)); break; } case 'F': case 'f': o = formatter.getFieldString(fieldLen, header .getFieldDecimalCount(col), (obj instanceof Number ? (Number)obj : NULL_NUMBER)); break; case 'D': case 'd': o = formatter.getFieldString((obj instanceof Date ? (Date)obj : null)); break; default: throw new IllegalStateException("Unknown type " + header.getFieldType(col)); } return o; } /** * Release resources associated with this writer. <B>Highly recommended</B> * * @throws java.io.IOException * If errors occur. */ public void close() throws IOException { // IANS - GEOT 193, bogus 0x00 written. According to dbf spec, optional // eof 0x1a marker is, well, optional. Since the original code wrote a // 0x00 (which is wrong anyway) lets just do away with this :) // - produced dbf works in OpenOffice and ArcExplorer java, so it must // be okay. // buffer.position(0); // buffer.put((byte) 0).position(0).limit(1); // write(); if (channel.isOpen()) { channel.close(); } buffer = null; channel = null; formatter = null; } /** Utility for formatting Dbase fields. */ public static class FieldFormatter { private StringBuffer buffer = new StringBuffer(255); private NumberFormat numFormat = NumberFormat .getNumberInstance(Locale.US); private Calendar calendar = Calendar.getInstance(Locale.US); private String emptyString; private static final int MAXCHARS = 255; private Charset charset; public FieldFormatter(Charset charset) { // Avoid grouping on number format numFormat.setGroupingUsed(false); // build a 255 white spaces string StringBuilder sb = new StringBuilder(MAXCHARS); sb.setLength(MAXCHARS); for (int i = 0; i < MAXCHARS; i++) { sb.setCharAt(i, ' '); } this.charset = charset; emptyString = sb.toString(); } public String getFieldString(int size, String s) { try { buffer.replace(0, size, emptyString); buffer.setLength(size); // international characters must be accounted for so size != // length. int maxSize = size; if (s != null) { buffer.replace(0, size, s); int currentBytes = s.substring(0, Math.min(size, s.length())) .getBytes(charset.name()).length; if (currentBytes > size) { char[] c = new char[1]; for (int index = size - 1; currentBytes > size; index--) { if (buffer.length() > index) { c[0] = buffer.charAt(index); String string = new String(c); buffer.deleteCharAt(index); currentBytes -= string.getBytes().length; maxSize--; } } } else { if (s.length() < size) { maxSize = size - (currentBytes - s.length()); for (int i = s.length(); i < size; i++) { buffer.append(' '); } } } } buffer.setLength(maxSize); return buffer.toString(); } catch (UnsupportedEncodingException e) { throw new IllegalStateException("This error should never happen...", e); } } public String getFieldString(Date d) { if (d != null) { buffer.delete(0, buffer.length()); calendar.setTime(d); int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH) + 1; // returns 0 // based month? int day = calendar.get(Calendar.DAY_OF_MONTH); if (year < 1000) { if (year >= 100) { buffer.append('0'); } else if (year >= 10) { buffer.append("00"); } else { buffer.append("000"); } } buffer.append(year); if (month < 10) { buffer.append('0'); } buffer.append(month); if (day < 10) { buffer.append('0'); } buffer.append(day); } else { buffer.setLength(8); buffer.replace(0, 8, emptyString); } buffer.setLength(8); return buffer.toString(); } public String getFieldString(int size, int decimalPlaces, Number n) { buffer.delete(0, buffer.length()); if (n != null) { numFormat.setMaximumFractionDigits(decimalPlaces); numFormat.setMinimumFractionDigits(decimalPlaces); numFormat.format(n, buffer, new FieldPosition( NumberFormat.INTEGER_FIELD)); } int diff = size - buffer.length(); if (diff >= 0) { while (diff-- > 0) { buffer.insert(0, ' '); } } else { buffer.setLength(size); } return buffer.toString(); } } }