001: /*
002: * DBFHeader.java
003: * tinySQL, manipulation of the first 32 bytes of a dBase III header
004: *
005: * $Author: davis $
006: * $Date: 2004/12/18 21:31:13 $
007: * $Revision: 1.1 $
008: *
009: */
010: package com.sqlmagic.tinysql;
011:
012: import java.util.*;
013: import java.lang.*;
014: import java.io.*;
015:
016: /**
017: dBase III header read/write access (bytes 0 - 31) <br>
018: The column definitions are not read
019: @author Brian Jepson <bjepson@home.com>
020: @author Marcel Ruff <ruff@swand.lake.de> Added write access to dBase and JDK 2 support
021: */
022: public class DBFHeader {
023: String tableName = null;
024: short file_type = 0; // = 0x03 without .DBT, 0x83 with .DBT (memo file)
025: short file_update_year = 0;
026: short file_update_month = 0;
027: short file_update_day = 0;
028: int numFields = 0; // number of column definitions
029: int numRecords = 0; // number of data records
030: int headerLength = 0; // in bytes
031: int recordLength = 0; // length in bytes of one data row, including the beginning delete flag-byte
032:
033: /*
034: The dBase III header consists of 32 byte bulks:
035: 0-32 primary header info
036: 32 bytes for each column info (n times)
037: The number of columns is calculated from the headerLength
038: */
039: final static int BULK_SIZE = 32; //
040: final static int FLAG_INDEX = 0; // = 0x03 without .DBT, 0x83 with .DBT (memo file)
041: final static int DATE_INDEX = 1; // 1=YY 2=MM 3=DD (last update)
042: final static int NUMBER_OF_REC_INDEX = 4; // 4-7
043: final static int LENGTH_OF_HEADER_INDEX = 8; // 8-9
044: final static int LENGTH_OF_REC_INDEX = 10; // 8-11
045: final static int RESERVED_INDEX = 12; // 12-31
046:
047: /**
048: * Constructs a DBFHeader, read the data from file <br>
049: * You need to supply an open file handle to read from
050: * @param ff open file handle for read access
051: */
052: DBFHeader(RandomAccessFile ff) throws tinySQLException {
053: try {
054: ff.seek(FLAG_INDEX);
055: file_type = Utils.fixByte(ff.readByte());
056:
057: // get the last update date
058: file_update_year = Utils.fixByte(ff.readByte());
059: file_update_month = Utils.fixByte(ff.readByte());
060: file_update_day = Utils.fixByte(ff.readByte());
061:
062: // a byte array to hold little-endian long data
063: //
064: byte[] b = new byte[4];
065:
066: // read that baby in...
067: //
068: ff.readFully(b);
069:
070: // convert the byte array into a long (really a double)
071: // 4-7 number of records
072: numRecords = (int) Utils.vax_to_long(b);
073:
074: // a byte array to hold little-endian short data
075: //
076: b = new byte[2];
077:
078: // get the data position (where it starts in the file)
079: // 8-9 Length of header
080: ff.readFully(b);
081: headerLength = Utils.vax_to_short(b);
082:
083: // find out the length of the data portion
084: // 10-11 Length of Record
085: ff.readFully(b);
086: recordLength = Utils.vax_to_short(b);
087:
088: // calculate the number of fields
089: //
090: numFields = (int) (headerLength - 33) / 32;
091:
092: // skip the next 20 bytes - looks like this is not needed...
093: //ff.skipBytes(20);
094: // 12-31 reserved
095:
096: Utils.log("HEADER=" + this .toString());
097:
098: } catch (Exception e) {
099: throw new tinySQLException(e.getMessage());
100: }
101: }
102:
103: /**
104: * Constructs a DBFHeader, read the data from file <br>
105: * You need to supply an open file handle to read from
106: * @param numFields number of Columns
107: * @param recordLength sum of all column.size plus 1 byte (delete flag)
108: */
109: DBFHeader(int numFields, int recordLength) throws tinySQLException {
110: this .numFields = numFields;
111: this .recordLength = recordLength;
112: Utils.log("DBFHeader", "numFields=" + numFields
113: + " recordLength=" + recordLength);
114: }
115:
116: /**
117: Create new dBase file and write the first 32 bytes<br>
118: the file remains opened
119: @return file handle with read/write access
120: */
121: public RandomAccessFile create(String dataDir, String tableName)
122: throws tinySQLException {
123: this .tableName = tableName;
124:
125: try {
126: // make the data directory, if it needs to be make
127: //
128: mkDataDirectory(dataDir);
129:
130: // perform an implicit drop table.
131: //
132: dropTable(dataDir, tableName);
133:
134: String fullPath = dataDir + File.separator + tableName
135: + dbfFileTable.dbfExtension;
136: RandomAccessFile ff = new RandomAccessFile(fullPath, "rw");
137:
138: write(ff);
139:
140: // ftbl.close();
141:
142: return ff;
143:
144: } catch (Exception e) {
145: throw new tinySQLException(e.getMessage());
146: }
147: }
148:
149: /**
150: write the first 32 bytes to file
151: */
152: public void write(RandomAccessFile ff) throws tinySQLException {
153: try {
154: //-----------------------------
155: // write out the primary header
156: ff.seek(FLAG_INDEX);
157: ff.writeByte((byte) 0x03);
158:
159: setTimestamp(ff); // set current date YY MM DD (dBase is not Y2K save)
160:
161: setNumRecords(ff, 0);
162:
163: setHeaderLength(ff, numFields);
164:
165: setRecordLength(ff, recordLength);
166:
167: setReserved(ff);
168:
169: } catch (Exception e) {
170: throw new tinySQLException(e.getMessage());
171: }
172: }
173:
174: /*
175: * Make the data directory unless it already exists
176: */
177: void mkDataDirectory(String dataDir) throws NullPointerException {
178: File dd = new File(dataDir);
179:
180: if (!dd.exists()) {
181: dd.mkdir();
182: }
183: }
184:
185: public void setTimestamp(RandomAccessFile ff)
186: throws tinySQLException {
187: try {
188: java.util.Calendar cal = java.util.Calendar.getInstance();
189: cal.setTime(new java.util.Date());
190: int dd = cal.get(java.util.Calendar.DAY_OF_MONTH);
191: int mm = cal.get(java.util.Calendar.MONTH) + 1;
192: int yy = cal.get(java.util.Calendar.YEAR);
193: yy = yy % 100; // Y2K problem: only 2 digits
194: ff.seek(DATE_INDEX);
195: ff.write(yy);
196: ff.write(mm);
197: ff.write(dd);
198: } catch (Exception e) {
199: throw new tinySQLException(e.getMessage());
200: }
201: }
202:
203: /**
204: Update the header (index 4-7) with the new number of records
205: @param New number of records
206: */
207: public void setNumRecords(RandomAccessFile ff, int numRecords)
208: throws tinySQLException {
209: this .numRecords = numRecords;
210: writeNumRecords(ff, numRecords);
211: }
212:
213: /**
214: Update the header (index 4-7) with the new number of records <br>
215: This is the static variant (use it if you don't want to obtain
216: a DBFHeader instance
217: @param New number of records
218: */
219: public static void writeNumRecords(RandomAccessFile ff,
220: int numRecords) throws tinySQLException {
221: try {
222: byte[] b = Utils.intToLittleEndian(numRecords);
223: ff.seek(NUMBER_OF_REC_INDEX);
224: ff.write(b);
225: } catch (Exception e) {
226: throw new tinySQLException(e.getMessage());
227: }
228: }
229:
230: /**
231: Update the header (index 8-9) with the new number of records
232: @param numFields number of columns (used to calculate header length)
233: */
234: public void setHeaderLength(RandomAccessFile ff, int numFields)
235: throws tinySQLException {
236: this .numFields = numFields;
237: try {
238: int headerLength = (DBFHeader.BULK_SIZE + 1) + numFields
239: * DBFHeader.BULK_SIZE;
240: ff.seek(DBFHeader.LENGTH_OF_HEADER_INDEX);
241: ff.write(Utils.shortToLittleEndian((short) headerLength));
242: } catch (Exception e) {
243: throw new tinySQLException(e.getMessage());
244: }
245: }
246:
247: /**
248: Update the header (index 10-11) with the length of one record
249: @param recordLength Length of one data record (row)
250: */
251: public void setRecordLength(RandomAccessFile ff, int recordLength)
252: throws tinySQLException {
253: this .recordLength = recordLength;
254: try {
255: ff.seek(DBFHeader.LENGTH_OF_REC_INDEX);
256: ff.write(Utils.shortToLittleEndian((short) recordLength));
257: } catch (Exception e) {
258: throw new tinySQLException(e.getMessage());
259: }
260: }
261:
262: /**
263: Update the header (index 10-11) with the length of one record
264: @param recordLength Length of one data record (row)
265: */
266: public void setReserved(RandomAccessFile ff)
267: throws tinySQLException {
268: try {
269: ff.seek(DBFHeader.RESERVED_INDEX);
270: byte[] reserved = Utils.forceToSize(null,
271: DBFHeader.BULK_SIZE - DBFHeader.RESERVED_INDEX,
272: (byte) 0);
273: ff.write(reserved); // padding with \0!
274: } catch (Exception e) {
275: throw new tinySQLException(e.getMessage());
276: }
277: }
278:
279: static void dropTable(String dataDir, String fname)
280: throws tinySQLException {
281: try {
282:
283: // delFile(fname);
284: Utils.delFile(dataDir, fname + dbfFileTable.dbfExtension);
285:
286: } catch (Exception e) {
287: throw new tinySQLException(e.getMessage());
288: }
289: }
290:
291: }
|