001: package org.geotools.dbffile;
002:
003: import java.io.*;
004: import java.net.URL;
005: import java.net.URLConnection;
006: import java.util.Vector;
007:
008: import com.vividsolutions.jump.io.EndianDataInputStream;
009:
010: /**
011: *
012: * This class represents a DBF (or DBase) file.<p>
013: * Construct it with a URL or File (including the .dbf)
014: * this causes the header and field definitions to be read.<p>
015: * Later queries return rows or columns of the database.<p>
016: * If a URL is specified then the whole file is read into memory<br>
017: * if a file is specified then a randomAccess system is used.<br>
018: *<hr>
019: * @author <a href="mailto:ian@geog.leeds.ac.uk">Ian Turton</a> Centre for
020: * Computaional Geography, University of Leeds, LS2 9JT, 1998.
021: * <br>
022: * mod to getStringCol by James Macgill.
023: */
024: public class Dbf implements DbfConsts {
025: static final boolean DEBUG = false;
026: static final String DBC = "Dbf->";
027: int dbf_id;
028: int last_update_d, last_update_m, last_update_y;
029: int last_rec;
030: int data_offset;
031: int rec_size;
032: StringBuffer records[];
033: int position = 0;
034: boolean hasmemo;
035: boolean isFile = false;
036: RandomAccessFile rFile;
037: EndianDataInputStream dFile;
038: int filesize, numfields;
039: public DbfFieldDef fielddef[];
040:
041: /**
042: * Constructor, opens the file and reads the header infomation.
043: * @param url the url to be opened
044: * @exception java.io.IOException If the file can't be opened.
045: * @exception DbfFileException If there is an error reading header.
046: */
047: public Dbf(URL url) throws java.io.IOException, DbfFileException {
048: if (DEBUG)
049: System.out
050: .println("---->uk.ac.leeds.ccg.dbffile.Dbf constructed. Will identify itself as "
051: + DBC);
052: URLConnection uc = url.openConnection();
053: InputStream in = uc.getInputStream();
054: EndianDataInputStream sfile = new EndianDataInputStream(in);
055: init(sfile);
056: }
057:
058: public Dbf(InputStream in) throws java.io.IOException,
059: DbfFileException {
060: if (DEBUG)
061: System.out
062: .println("---->uk.ac.leeds.ccg.dbffile.Dbf constructed. Will identify itself as "
063: + DBC);
064: EndianDataInputStream sfile = new EndianDataInputStream(in);
065: init(sfile);
066: }
067:
068: public Dbf(String name) throws java.io.IOException,
069: DbfFileException {
070: if (DEBUG)
071: System.out
072: .println("---->uk.ac.leeds.ccg.dbffile.Dbf constructed. Will identify itself as "
073: + DBC);
074: URL url = new URL(name);
075: URLConnection uc = url.openConnection();
076: InputStream in = uc.getInputStream();
077: EndianDataInputStream sfile = new EndianDataInputStream(in);
078: init(sfile);
079: }
080:
081: public Dbf(File file) throws java.io.IOException, DbfFileException {
082: if (DEBUG)
083: System.out
084: .println("---->uk.ac.leeds.ccg.dbffile.Dbf constructed. Will identify itself as "
085: + DBC);
086: InputStream in = new FileInputStream(file);
087: EndianDataInputStream sfile = new EndianDataInputStream(in);
088: rFile = new RandomAccessFile(file, "r");
089: isFile = true;
090: init(sfile);
091: }
092:
093: /**
094: * Returns the date of the last update of the file as a string.
095: */
096: public String getLastUpdate() {
097: String date = last_update_d + "/" + (last_update_m + 1) + "/"
098: + (1900 + last_update_y);
099: return date;
100: }
101:
102: /**
103: * Returns the number of records in the database file.
104: */
105: public int getLastRec() {
106: return last_rec;
107: }
108:
109: /**
110: * Returns the size of the records in the database file.
111: */
112: public int getRecSize() {
113: return rec_size;
114: }
115:
116: /**
117: * Returns the number of fields in the records in the database file.
118: */
119: public int getNumFields() {
120: return numfields;
121: }
122:
123: /**
124: * looks up the field number for the given named column
125: * @param name A String for the name to look up
126: * @return int The col number for the field, -1 if field could not be found
127: */
128: public int getFieldNumber(String name) {
129: for (int i = 0; i < numfields; i++) {
130: //System.out.println(i);
131: if (name.equalsIgnoreCase(fielddef[i].fieldname.toString())) {
132: return i;
133: }
134: }
135: return -1;//not found
136: }
137:
138: /**
139: * Returns the size of the database file.
140: */
141: public int getFileSize() {
142: return filesize;
143: }
144:
145: public StringBuffer getFieldName(int col) {
146: if (col >= numfields)
147: throw new IllegalArgumentException(
148: DBC
149: + "column number specified is invalid. It's higher than the amount of columns available "
150: + numfields);
151: return fielddef[col].fieldname;
152: }
153:
154: public char getFieldType(int col) {
155: if (col >= numfields)
156: throw new IllegalArgumentException(
157: DBC
158: + "column number specified is invalid. It's higher than the amount of columns available"
159: + numfields);
160: return fielddef[col].fieldtype;
161: }
162:
163: /**
164: * initailizer, allows the use of multiple constructers in later
165: * versions.
166: */
167:
168: private void init(EndianDataInputStream sfile) throws IOException,
169: DbfFileException {
170: DbfFileHeader head = new DbfFileHeader(sfile);
171: int widthsofar;
172:
173: dFile = sfile;
174:
175: fielddef = new DbfFieldDef[numfields];
176: widthsofar = 1;
177: for (int index = 0; index < numfields; index++) {
178: fielddef[index] = new DbfFieldDef();
179: fielddef[index].setup(widthsofar, sfile);
180: widthsofar += fielddef[index].fieldlen;
181: }
182: sfile.skipBytes(1); // end of field defs marker
183: if (!isFile) {
184: records = GrabFile();
185: }
186: }
187:
188: /**
189: * Internal Class to hold information from the header of the file
190: */
191: class DbfFileHeader {
192:
193: /**
194: * Reads the header of a dbf file.
195: * @param LEDataInputStream file Stream attached to the input file
196: * @exception IOException read error.
197: */
198: public DbfFileHeader(EndianDataInputStream file)
199: throws IOException {
200: getDbfFileHeader(file);
201: }
202:
203: private void getDbfFileHeader(EndianDataInputStream file)
204: throws IOException {
205:
206: int len;
207: dbf_id = (int) file.readUnsignedByteLE();
208: if (DEBUG)
209: System.out.println(DBC + "Header id " + dbf_id);
210: if (dbf_id == 3)
211: hasmemo = true;
212: else
213: hasmemo = false;
214:
215: last_update_y = (int) file.readUnsignedByteLE();
216: last_update_m = (int) file.readUnsignedByteLE();
217: last_update_d = (int) file.readUnsignedByteLE();
218: if (DEBUG)
219: System.out.print(DBC + "last update ");
220: if (DEBUG)
221: System.out.print(last_update_d);
222: if (DEBUG)
223: System.out.print("/");
224: if (DEBUG)
225: System.out.print(last_update_m);
226: if (DEBUG)
227: System.out.print("/");
228: if (DEBUG)
229: System.out.println(last_update_y);
230:
231: last_rec = file.readIntLE();
232: if (DEBUG)
233: System.out.print(DBC + "last rec ");
234: if (DEBUG)
235: System.out.println(last_rec);
236:
237: data_offset = file.readShortLE();
238: //data_offset=0;
239: //System.out.println("x = "+file.readUnsignedByte()+" " +
240: //file.readUnsignedByte());
241: if (DEBUG)
242: System.out.print(DBC + "data offset ");
243: if (DEBUG)
244: System.out.println(data_offset);
245:
246: rec_size = file.readShortLE();
247: if (DEBUG)
248: System.out.print(DBC + "rec_size ");
249: if (DEBUG)
250: System.out.println(rec_size);
251:
252: filesize = (rec_size * last_rec) + data_offset + 1;
253: numfields = (data_offset - DBF_BUFFSIZE - 1) / DBF_BUFFSIZE;
254:
255: if (DEBUG)
256: System.out.print(DBC + "num fields ");
257: if (DEBUG)
258: System.out.println(numfields);
259: if (DEBUG)
260: System.out.print(DBC + "file size ");
261: if (DEBUG)
262: System.out.println(filesize);
263: file.skipBytes(20);
264: }
265:
266: }
267:
268: /**
269: * gets the next record and returns it as a string. This method works on
270: * a sequential stream and can not go backwards. Only useful if you want
271: * to read the whole file in one.
272: * @exception java.io.IOException on read error.
273: */
274: public StringBuffer GetNextDbfRec() throws java.io.IOException {
275: return records[position++];
276: }
277:
278: private StringBuffer GrabNextDbfRec() throws java.io.IOException {
279: StringBuffer record = new StringBuffer(rec_size + numfields);
280:
281: //Modifed to use Hisaji ONO's approach for reading multi byte character sets
282: byte[] strbuf = new byte[rec_size]; // <---- byte array buffer fo storing string's byte data
283: for (int i = 0; i < rec_size; i++) {
284: strbuf[i] = dFile.readByteLE(); // <---- read string's byte data
285: }
286: record.append(new String(strbuf)); // <- append byte array to String Buffer
287:
288: return record;
289: }
290:
291: private StringBuffer[] GrabFile() throws java.io.IOException {
292: StringBuffer records[] = new StringBuffer[last_rec];
293: for (int i = 0; i < last_rec; i++)
294: records[i] = GrabNextDbfRec();
295: return records;
296: }
297:
298: /**
299: * fetches the <i>row</i>th row of the file
300: * @param row - the row to fetch
301: * @exception java.io.IOException on read error.
302: */
303: public StringBuffer GetDbfRec(int row) throws java.io.IOException {
304: StringBuffer record;// = new StringBuffer(rec_size);
305: if (!isFile) {
306: return record = new StringBuffer(records[row].toString());
307: } else {
308: record = new StringBuffer(rec_size + numfields);
309:
310: rFile.seek(data_offset + (rec_size * row));
311: //Modifed to use Hisaji ONO's approach for reading multi byte character sets
312: byte[] strbuf = new byte[rec_size]; // <---- byte array buffer fo storing string's byte data
313: for (int i = 0; i < rec_size; i++) {
314: strbuf[i] = dFile.readByteLE(); // <---- read string's byte data
315: }
316: record.append(new String(strbuf)); // <- append byte array to String Buffer
317: return record;
318: }
319:
320: }
321:
322: /**
323: * fetches the <i>row</i>th row of the file and parses it into an vector
324: * of objects.
325: * @param row - the row to fetch
326: * @exception java.io.IOException on read error.
327: */
328: public Vector ParseDbfRecord(int row) throws java.io.IOException {
329: return ParseRecord(GetDbfRec(row));
330: }
331:
332: /**
333: * Parses the record stored in the StringBuffer rec into a vector of
334: * objects
335: * @param rec the record to be parsed.
336: */
337:
338: public Vector ParseRecord(StringBuffer rec) {
339: Vector record = new Vector(numfields);
340: String t;
341: Integer I = new Integer(0);
342: Float F = new Float(0.0);
343: t = rec.toString();
344: for (int i = 0; i < numfields; i++) {
345: if (DEBUG)
346: System.out.println(DBC + "type "
347: + fielddef[i].fieldtype);
348: if (DEBUG)
349: System.out.println(DBC + "start "
350: + fielddef[i].fieldstart);
351: if (DEBUG)
352: System.out.println(DBC + "len " + fielddef[i].fieldlen);
353: if (DEBUG)
354: System.out.println(DBC
355: + ""
356: + t.substring(fielddef[i].fieldstart,
357: fielddef[i].fieldstart
358: + fielddef[i].fieldlen));
359: switch (fielddef[i].fieldtype) {
360: case 'C':
361: record.addElement(t.substring(fielddef[i].fieldstart,
362: fielddef[i].fieldstart + fielddef[i].fieldlen));
363: break;
364: case 'N':
365: case 'F':
366: if (fielddef[i].fieldnumdec == 0)
367: try {
368: record.addElement(I.decode(t.substring(
369: fielddef[i].fieldstart,
370: fielddef[i].fieldstart
371: + fielddef[i].fieldlen)));
372: } catch (java.lang.NumberFormatException e) {
373: record.addElement(new Integer(0));
374: }
375: else
376: try {
377: record.addElement(F.valueOf(t.substring(
378: fielddef[i].fieldstart,
379: fielddef[i].fieldstart
380: + fielddef[i].fieldlen)));
381: } catch (java.lang.NumberFormatException e) {
382: record.addElement(new Float(0.0));
383: }
384:
385: break;
386: default:
387: if (DEBUG)
388: System.out.println(DBC
389: + "Oh - don't know how to parse "
390: + fielddef[i].fieldtype);
391: }
392: }
393: return record;
394: }
395:
396: /**
397: * Fetches a column of Integers from the database file.
398: * @param int col - the column to fetch
399: * @exception java.io.IOException - on read error
400: * @exception DbfFileException - column is not an Integer.
401: */
402: public Integer[] getIntegerCol(int col) throws java.io.IOException,
403: DbfFileException {
404: return getIntegerCol(col, 0, last_rec);
405: }
406:
407: /**
408: * Fetches a part column of Integers from the database file.
409: * @param col - the column to fetch
410: * @param start - the row to start fetching from
411: * @param end - the row to stop fetching at.
412: * @exception java.io.IOException - on read error
413: * @exception DbfFileException - column is not an Integer.
414: */
415: public Integer[] getIntegerCol(int col, int start, int end)
416: throws java.io.IOException, DbfFileException {
417: Integer column[] = new Integer[end - start];
418: String record = new String();
419: StringBuffer sb = new StringBuffer(numfields);
420: int k = 0, i = 0;
421: if (col >= numfields)
422: throw new DbfFileException(DBC + "No Such Column in file: "
423: + col);
424: if (fielddef[col].fieldtype != 'N')
425: throw new DbfFileException(DBC + "Column " + col
426: + " is not Integer " + fielddef[col].fieldtype);
427: if (start < 0)
428: throw new DbfFileException(DBC + "Start must be >= 0");
429: if (end > last_rec)
430: throw new DbfFileException(DBC + "End must be <= "
431: + last_rec);
432: // move to start of data
433: try {
434: for (i = start; i < end; i++) {
435: sb.setLength(0);
436: sb = GetDbfRec(i);
437: record = sb.toString();
438: if (DEBUG)
439: System.out.println(DBC
440: + record.substring(
441: fielddef[col].fieldstart,
442: fielddef[col].fieldstart
443: + fielddef[col].fieldlen)
444: .trim() + "*");
445: column[i - start] = new Integer(record.substring(
446: fielddef[col].fieldstart,
447: fielddef[col].fieldstart
448: + fielddef[col].fieldlen).trim());
449: }
450: } catch (java.lang.NumberFormatException nfe) {
451: //throw new DbfFileException(DBC+"Column "+col+" contains an non integer id number "+nfe);
452: // be nicer
453: column[i - start] = new Integer(0);
454: } catch (java.io.EOFException e) {
455: System.err.println(e);
456: System.err.println("Dbf->record " + i + " byte " + k
457: + " file pos ");
458: } catch (java.io.IOException e) {
459: System.err.println(e);
460: System.err.println("Dbf->record " + i + " byte " + k
461: + " file pos ");
462: }
463: return column;
464: }
465:
466: /**
467: * Fetches a column of Floats from the database file.
468: * @param col - the column to fetch
469: * @exception java.io.IOException - on read error
470: * @exception DbfFileException - column is not an Integer.
471: */
472: public Float[] getFloatCol(int col) throws DbfFileException,
473: java.io.IOException {
474: return getFloatCol(col, 0, last_rec);
475: }
476:
477: /**
478: * Fetches a part column of Floats from the database file.
479: * @param col - the column to fetch
480: * @param start - the row to start fetching from
481: * @param end - the row to stop fetching at.
482: * @exception java.io.IOException - on read error
483: * @exception DbfFileException - column is not an Integer.
484: */
485: public Float[] getFloatCol(int col, int start, int end)
486: throws DbfFileException, java.io.IOException {
487: Float column[] = new Float[end - start];
488: String record, st;
489: StringBuffer sb = new StringBuffer(rec_size);
490: int k = 0, i = 0;
491: if (col >= numfields)
492: throw new DbfFileException("Dbf->No Such Column in file: "
493: + col);
494: if (fielddef[col].fieldtype != 'F'
495: && fielddef[col].fieldtype != 'N')
496: throw new DbfFileException("Dbf->Column " + col
497: + " is not Float " + fielddef[col].fieldtype);
498: if (start < 0)
499: throw new DbfFileException("Dbf->Start must be >= 0");
500: if (end > last_rec)
501: throw new DbfFileException("Dbf->End must be <= "
502: + last_rec);
503: // move to start of data
504: try {
505: for (i = start; i < end; i++) {
506: sb.setLength(0);
507: sb = GetDbfRec(i);
508: record = sb.toString();
509: st = new String(record.substring(
510: fielddef[col].fieldstart,
511: fielddef[col].fieldstart
512: + fielddef[col].fieldlen)).trim();
513: if (st.indexOf('.') == -1) {
514: st = st + ".0";
515: }
516: try {
517: column[i - start] = new Float(st);
518: } catch (java.lang.NumberFormatException e) {
519: column[i - start] = new Float(0.0);
520: }
521: }
522: } catch (java.io.EOFException e) {
523: System.err.println("Dbf->" + e);
524: System.err.println("Dbf->record " + i + " byte " + k
525: + " file pos ");
526: } catch (java.io.IOException e) {
527: System.err.println("Dbf->" + e);
528: System.err.println("Dbf->record " + i + " byte " + k
529: + " file pos ");
530: }
531:
532: return column;
533: }
534:
535: /**
536: * Fetches a column of Strings from the database file.
537: * @param col - the column to fetch
538: * @exception java.io.IOException - on read error
539: * @exception DbfFileException - column is not an Integer.
540: */
541: public String[] getStringCol(int col) throws DbfFileException,
542: java.io.IOException {
543: return getStringCol(col, 0, last_rec);
544: }
545:
546: /**
547: * Fetches a part column of Strings from the database file.
548: * @param col - the column to fetch
549: * @param start - the row to start fetching from
550: * @param end - the row to stop fetching at.
551: * @exception java.io.IOException - on read error
552: * @exception DbfFileException - column is not an Integer.
553: */
554: public String[] getStringCol(int col, int start, int end)
555: throws DbfFileException, java.io.IOException {
556: String column[] = new String[end - start];
557: String record = new String();
558: StringBuffer sb = new StringBuffer(numfields);
559: int k = 0, i = 0;
560: if (col >= numfields)
561: throw new DbfFileException("Dbf->No Such Column in file: "
562: + col);
563: //if(fielddef[col].fieldtype!='C')
564: //throw new DbfFileException("Column "+col+" is not a String");
565: if (start < 0)
566: throw new DbfFileException("Dbf->Start must be >= 0");
567: if (end > last_rec)
568: throw new DbfFileException("Dbf->End must be <= "
569: + last_rec);
570: // move to start of data
571: try {
572: for (i = start; i < end; i++) {
573: sb.setLength(0);
574: sb = GetDbfRec(i);
575: record = sb.toString();
576: //column[i-start]=new String(record.substring(fielddef[col].fieldstart,
577: //fielddef[col].fieldstart+fielddef[col].fieldlen));
578: // replaced to fix bug #547080
579: column[i - start] = new String(record.getBytes(),
580: fielddef[col].fieldstart,
581: fielddef[col].fieldlen).trim();
582:
583: }
584: } catch (java.io.EOFException e) {
585: System.err.println("Dbf->" + e);
586: System.err.println("Dbf->record " + i + " byte " + k
587: + " file pos ");
588: } catch (java.io.IOException e) {
589: System.err.println("Dbf->" + e);
590: System.err.println("Dbf->record " + i + " byte " + k
591: + " file pos ");
592: }
593: return column;
594: }
595: }
|