View Javadoc

1   /*
2    * DbfReader.java
3    * 
4    * Created on October 11, 2002 Last edited on October 11, 2002
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 		//Let's see if we may cache..
131 		if (this.cachedContent != null && this.cache)
132 		{
133 			return this.cachedContent[rowNumber];
134 		}
135 
136 		//Either we may not cache of the cache is still empty
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 		// Let's see if we may cache..
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 }