001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2002, Centre for Computational Geography
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library 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: * This file is based on an origional contained in the GISToolkit project:
018: * http://gistoolkit.sourceforge.net/
019: */
020: package org.geotools.data.shapefile.dbf;
021:
022: import java.io.IOException;
023: import java.nio.ByteBuffer;
024: import java.nio.MappedByteBuffer;
025: import java.nio.channels.WritableByteChannel;
026: import java.text.FieldPosition;
027: import java.text.NumberFormat;
028: import java.util.Calendar;
029: import java.util.Date;
030: import java.util.Locale;
031:
032: import org.geotools.data.shapefile.StreamLogging;
033: import org.geotools.resources.NIOUtilities;
034:
035: /** A DbaseFileReader is used to read a dbase III format file.
036: * The general use of this class is:
037: * <CODE><PRE>
038: * DbaseFileHeader header = ...
039: * WritableFileChannel out = new FileOutputStream("thefile.dbf").getChannel();
040: * DbaseFileWriter w = new DbaseFileWriter(header,out);
041: * while ( moreRecords ) {
042: * w.write( getMyRecord() );
043: * }
044: * w.close();
045: * </PRE></CODE>
046: * You must supply the <CODE>moreRecords</CODE> and <CODE>getMyRecord()</CODE>
047: * logic...
048: * @author Ian Schneider
049: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/shapefile/src/main/java/org/geotools/data/shapefile/dbf/DbaseFileWriter.java $
050: */
051: public class DbaseFileWriter {
052:
053: private DbaseFileHeader header;
054: private DbaseFileWriter.FieldFormatter formatter = new DbaseFileWriter.FieldFormatter();
055: WritableByteChannel channel;
056: private ByteBuffer buffer;
057: private final Number NULL_NUMBER = new Integer(0);
058: private final String NULL_STRING = "";
059: private final Date NULL_DATE = new Date();
060: private StreamLogging streamLogger = new StreamLogging(
061: "Dbase File Writer");
062:
063: /** Create a DbaseFileWriter using the specified header and writing to the given
064: * channel.
065: * @param header The DbaseFileHeader to write.
066: * @param out The Channel to write to.
067: * @throws IOException If errors occur while initializing.
068: */
069: public DbaseFileWriter(DbaseFileHeader header,
070: WritableByteChannel out) throws IOException {
071: header.writeHeader(out);
072: this .header = header;
073: this .channel = out;
074: streamLogger.open();
075: init();
076: }
077:
078: private void init() throws IOException {
079: buffer = ByteBuffer.allocateDirect(header.getRecordLength());
080: }
081:
082: private void write() throws IOException {
083: buffer.position(0);
084: int r = buffer.remaining();
085: while ((r -= channel.write(buffer)) > 0) {
086: ; // do nothing
087: }
088: }
089:
090: /** Write a single dbase record.
091: * @param record The entries to write.
092: * @throws IOException If IO error occurs.
093: * @throws DbaseFileException If the entry doesn't comply to the header.
094: */
095: public void write(Object[] record) throws IOException,
096: DbaseFileException {
097:
098: if (record.length != header.getNumFields()) {
099: throw new DbaseFileException("Wrong number of fields "
100: + record.length + " expected "
101: + header.getNumFields());
102: }
103:
104: buffer.position(0);
105:
106: // put the 'not-deleted' marker
107: buffer.put((byte) ' ');
108:
109: for (int i = 0; i < header.getNumFields(); i++) {
110: String fieldString = fieldString(record[i], i);
111: if (header.getFieldLength(i) != fieldString.getBytes().length) {
112: //System.out.println(i + " : " + header.getFieldName(i)+" value = "+fieldString+"");
113: buffer.put(new byte[header.getFieldLength(i)]);
114: } else {
115: buffer.put(fieldString.getBytes());
116: }
117:
118: }
119:
120: write();
121: }
122:
123: private String fieldString(Object obj, final int col) {
124: String o;
125: final int fieldLen = header.getFieldLength(col);
126: switch (header.getFieldType(col)) {
127: case 'C':
128: case 'c':
129: o = formatter.getFieldString(fieldLen,
130: obj == null ? NULL_STRING : obj.toString());
131: break;
132: case 'L':
133: case 'l':
134: o = (obj == null ? "F" : obj == Boolean.TRUE ? "T" : "F");
135: // o = formatter.getFieldString(
136: // fieldLen,
137: // o
138: // );
139: break;
140: case 'M':
141: case 'G':
142: o = formatter.getFieldString(fieldLen,
143: obj == null ? NULL_STRING : obj.toString());
144: break;
145: case 'N':
146: case 'n':
147: // int?
148: if (header.getFieldDecimalCount(col) == 0) {
149:
150: o = formatter.getFieldString(fieldLen, 0,
151: (Number) (obj == null ? NULL_NUMBER : obj));
152: break;
153: }
154: case 'F':
155: case 'f':
156: o = formatter.getFieldString(fieldLen, header
157: .getFieldDecimalCount(col),
158: (Number) (obj == null ? NULL_NUMBER : obj));
159: break;
160: case 'D':
161: case 'd':
162: o = formatter
163: .getFieldString((Date) (obj == null ? NULL_DATE
164: : obj));
165: break;
166: default:
167: throw new RuntimeException("Unknown type "
168: + header.getFieldType(col));
169: }
170:
171: return o;
172: }
173:
174: /** Release resources associated with this writer.
175: * <B>Highly recommended</B>
176: * @throws IOException If errors occur.
177: */
178: public void close() throws IOException {
179: // IANS - GEOT 193, bogus 0x00 written. According to dbf spec, optional
180: // eof 0x1a marker is, well, optional. Since the original code wrote a
181: // 0x00 (which is wrong anyway) lets just do away with this :)
182: // - produced dbf works in OpenOffice and ArcExplorer java, so it must
183: // be okay.
184: // buffer.position(0);
185: // buffer.put((byte) 0).position(0).limit(1);
186: // write();
187: if (channel.isOpen()) {
188: channel.close();
189: streamLogger.close();
190: }
191: if (buffer instanceof MappedByteBuffer) {
192: NIOUtilities.clean(buffer);
193: }
194: buffer = null;
195: channel = null;
196: formatter = null;
197: }
198:
199: /** Utility for formatting Dbase fields. */
200: public static class FieldFormatter {
201: private StringBuffer buffer = new StringBuffer(255);
202: private NumberFormat numFormat = NumberFormat
203: .getNumberInstance(Locale.US);
204: private Calendar calendar = Calendar.getInstance(Locale.US);
205: private String emptyString;
206: private static final int MAXCHARS = 255;
207:
208: public FieldFormatter() {
209: // Avoid grouping on number format
210: numFormat.setGroupingUsed(false);
211:
212: // build a 255 white spaces string
213: StringBuffer sb = new StringBuffer(MAXCHARS);
214: sb.setLength(MAXCHARS);
215: for (int i = 0; i < MAXCHARS; i++) {
216: sb.setCharAt(i, ' ');
217: }
218:
219: emptyString = sb.toString();
220: }
221:
222: public String getFieldString(int size, String s) {
223: buffer.replace(0, size, emptyString);
224: buffer.setLength(size);
225: //international characters must be accounted for so size != length.
226: int maxSize = size;
227: if (s != null) {
228: buffer.replace(0, size, s);
229: int currentBytes = s.substring(0,
230: Math.min(size, s.length())).getBytes().length;
231: if (currentBytes > size) {
232: char[] c = new char[1];
233: for (int index = size - 1; currentBytes > size; index--) {
234: c[0] = buffer.charAt(index);
235: String string = new String(c);
236: buffer.deleteCharAt(index);
237: currentBytes -= string.getBytes().length;
238: maxSize--;
239: }
240: } else {
241: if (s.length() < size) {
242: maxSize = size - (currentBytes - s.length());
243: for (int i = s.length(); i < size; i++) {
244: buffer.append(' ');
245: }
246: }
247: }
248: }
249:
250: buffer.setLength(maxSize);
251:
252: return buffer.toString();
253: }
254:
255: public String getFieldString(Date d) {
256:
257: if (d != null) {
258: buffer.delete(0, buffer.length());
259:
260: calendar.setTime(d);
261: int year = calendar.get(Calendar.YEAR);
262: int month = calendar.get(Calendar.MONTH) + 1; // returns 0 based month?
263: int day = calendar.get(Calendar.DAY_OF_MONTH);
264:
265: if (year < 1000) {
266: if (year >= 100) {
267: buffer.append("0");
268: } else if (year >= 10) {
269: buffer.append("00");
270: } else {
271: buffer.append("000");
272: }
273: }
274: buffer.append(year);
275:
276: if (month < 10) {
277: buffer.append("0");
278: }
279: buffer.append(month);
280:
281: if (day < 10) {
282: buffer.append("0");
283: }
284: buffer.append(day);
285: } else {
286: buffer.setLength(8);
287: buffer.replace(0, 8, emptyString);
288: }
289:
290: buffer.setLength(8);
291: return buffer.toString();
292: }
293:
294: public String getFieldString(int size, int decimalPlaces,
295: Number n) {
296: buffer.delete(0, buffer.length());
297:
298: if (n != null) {
299: numFormat.setMaximumFractionDigits(decimalPlaces);
300: numFormat.setMinimumFractionDigits(decimalPlaces);
301: numFormat.format(n, buffer, new FieldPosition(
302: NumberFormat.INTEGER_FIELD));
303: }
304:
305: int diff = size - buffer.length();
306: if (diff >= 0) {
307: while (diff-- > 0) {
308: buffer.insert(0, ' ');
309: }
310: } else {
311: buffer.setLength(size);
312: }
313: return buffer.toString();
314: }
315: }
316:
317: }
|