1
2
3
4
5
6 package nl.javel.gisbeans.io.esri;
7
8 import java.io.IOException;
9 import java.io.Serializable;
10 import java.net.URL;
11 import java.util.ArrayList;
12
13 import nl.javel.gisbeans.io.EndianInterface;
14 import nl.javel.gisbeans.io.ObjectEndianInputStream;
15
16 /***
17 * This class reads a dbf file (dBaseIII) used in Esri ShapeFiles
18 *
19 * @author <a href="mailto:paul.jacobs@javel.nl">Paul Jacobs </a> <br>
20 * <a href="mailto:peter.jacobs@javel.nl">Peter Jacobs </a>
21 * @since JDK 1
22 * @version 1
23 */
24 public class DbfReader implements Serializable
25 {
26 /*** the FBFFile */
27 private URL dbfFile;
28
29 /*** the numberofColumns */
30 private int[] columnLength;
31
32 /*** the names of the columns */
33 private String[] columnNames;
34
35 /*** the headerLength */
36 private int headerLength = 0;
37
38 /*** the number of columns */
39 private int numColumns = 0;
40
41 /*** the number of records */
42 private int numRecords = 0;
43
44 /*** the length of the records */
45 private int recordLength = 0;
46
47 /*** the cachedContent */
48 private transient String[][] cachedContent = null;
49
50 /*** may we cache parsed data?.. */
51 private boolean cache = true;
52
53 /***
54 * constructs a DbfReader
55 *
56 * @param dbfFile the URL of the dbfFile
57 * @throws IOException whenever url does not occur to exist.
58 */
59 public DbfReader(URL dbfFile) throws IOException
60 {
61 this.dbfFile = dbfFile;
62 ObjectEndianInputStream dbfInput = new ObjectEndianInputStream(dbfFile
63 .openStream());
64 if (dbfInput.readByte() != 3)
65 throw new IOException(
66 "dbf file does not seem to be a Dbase III file");
67
68 dbfInput.skipBytes(3);
69 dbfInput.setEncode(EndianInterface.LITTLE_ENDIAN);
70
71 this.numRecords = dbfInput.readInt();
72 this.headerLength = dbfInput.readShort();
73 this.numColumns = (this.headerLength - 33) / 32;
74 this.recordLength = dbfInput.readShort();
75
76 this.columnLength = new int[this.numColumns];
77 this.columnNames = new String[this.numColumns];
78
79 dbfInput.skipBytes(20);
80 for (int i = 0; i < this.numColumns; i++)
81 {
82 StringBuffer buffer = new StringBuffer();
83 for (int j = 0; j < 10; j++)
84 {
85 byte b = dbfInput.readByte();
86 if (b > 31)
87 buffer.append((char) b);
88 }
89 this.columnNames[i] = buffer.toString();
90 dbfInput.setEncode(EndianInterface.BIG_ENDIAN);
91 dbfInput.readChar();
92
93 dbfInput.skipBytes(4);
94
95 this.columnLength[i] = dbfInput.readByte();
96 if (this.columnLength[i] < 0)
97 this.columnLength[i] += 256;
98 dbfInput.skipBytes(15);
99 }
100 dbfInput.close();
101 }
102
103 /***
104 * returns the columnNames
105 *
106 * @return String[] the columnNames
107 */
108 public String[] getColumnNames()
109 {
110 return this.columnNames;
111 }
112
113 /***
114 * returns the row
115 *
116 * @param rowNumber the rowNumber
117 * @return String[] the attributes of the row
118 * @throws IOException
119 * @throws IndexOutOfBoundsException whenever the rowNumber > numRecords
120 */
121 public String[] getRow(int rowNumber) throws IOException,
122 IndexOutOfBoundsException
123 {
124 if (rowNumber > this.numRecords)
125 {
126 throw new IndexOutOfBoundsException(
127 "dbfFile : rowNumber > numRecords");
128 }
129
130
131 if (this.cachedContent != null && this.cache)
132 {
133 return this.cachedContent[rowNumber];
134 }
135
136
137 String[] row = new String[this.numColumns];
138
139 ObjectEndianInputStream dbfInput = new ObjectEndianInputStream(
140 this.dbfFile.openStream());
141 dbfInput.skipBytes(this.headerLength + 1);
142 dbfInput.skipBytes(rowNumber * this.recordLength);
143 for (int i = 0; i < this.numColumns; i++)
144 {
145 byte[] bytes = new byte[this.columnLength[i]];
146 dbfInput.read(bytes);
147 row[i] = new String(bytes);
148 }
149 dbfInput.close();
150 return row;
151 }
152
153 /***
154 * returns a table of all attributes stored for the particular dbf-file
155 *
156 * @return String[][] a table of attributes
157 * @throws IOException an IOException
158 */
159 public String[][] getRows() throws IOException
160 {
161
162 if (this.cachedContent != null && this.cache)
163 {
164 return this.cachedContent;
165 }
166
167 String[][] result = new String[this.numRecords][this.numColumns];
168 ObjectEndianInputStream dbfInput = new ObjectEndianInputStream(
169 this.dbfFile.openStream());
170 dbfInput.skipBytes(this.headerLength + 1);
171 for (int row = 0; row < this.numRecords; row++)
172 {
173 for (int col = 0; col < this.numColumns; col++)
174 {
175 byte[] bytes = new byte[this.columnLength[col]];
176 dbfInput.read(bytes);
177 result[row][col] = new String(bytes);
178 if (col == this.numColumns - 1)
179 {
180 dbfInput.skipBytes(1);
181 }
182 }
183 }
184 dbfInput.close();
185 if (this.cache)
186 {
187 this.cachedContent = result;
188 }
189 return result;
190 }
191
192 /***
193 * returns the array of rowNumbers belonging to a attribute/column pair
194 *
195 * @param attribute the attribute value
196 * @param columnName the name of the column
197 * @return int[] the array of shape numbers.
198 * @throws IOException
199 */
200 public int[] getRowNumbers(String attribute, String columnName)
201 throws IOException
202 {
203 ArrayList result = new ArrayList();
204 String[][] rows = this.getRows();
205 for (int col = 0; col < this.numColumns; col++)
206 {
207 if (this.columnNames[col].equals(columnName))
208 {
209 for (int row = 0; row < this.numRecords; row++)
210 {
211 if (rows[row][col].equals(attribute))
212 result.add(new Integer(row));
213 }
214 }
215 }
216 int[] array = new int[result.size()];
217 for (int i = 0; i < array.length; i++)
218 {
219 array[i] = ((Integer) result.get(i)).intValue();
220 }
221 return array;
222 }
223
224 /***
225 * may we cache parsed data for a session. If false, every getRows results
226 * in IO activity. If true data is stored inMemory
227 *
228 * @return Returns the cache.
229 */
230 public boolean isCache()
231 {
232 return this.cache;
233 }
234
235 /***
236 * @param cache The cache to set.
237 */
238 public void setCache(boolean cache)
239 {
240 this.cache = cache;
241 }
242 }