001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.data.shapefile.indexed;
017:
018: import java.io.IOException;
019: import java.nio.ByteBuffer;
020: import java.nio.MappedByteBuffer;
021: import java.nio.channels.FileChannel;
022: import java.nio.channels.ReadableByteChannel;
023: import java.security.acl.LastOwnerException;
024: import java.util.NoSuchElementException;
025: import java.util.logging.Logger;
026:
027: import org.geotools.data.FIDReader;
028: import org.geotools.data.shapefile.StreamLogging;
029: import org.geotools.data.shapefile.shp.ShapefileReader;
030: import org.geotools.resources.NIOUtilities;
031:
032: /**
033: * This object reads from a file the fid of a feature in a shapefile.
034: *
035: * @author Jesse
036: */
037: public class IndexedFidReader implements FIDReader {
038: private static final Logger LOGGER = org.geotools.util.logging.Logging
039: .getLogger("org.geotools.data.shapefile");
040: private ReadableByteChannel readChannel;
041: private ByteBuffer buffer;
042: private long count;
043: private String typeName;
044: private boolean done;
045: private int removes;
046: private int currentShxIndex = -1;
047: private RecordNumberTracker reader;
048: private long currentId;
049: StreamLogging streamLogger = new StreamLogging("IndexedFidReader");
050:
051: public IndexedFidReader(String typeName,
052: ReadableByteChannel readChannel) throws IOException {
053: this .typeName = typeName + ".";
054: this .readChannel = readChannel;
055: streamLogger.open();
056: getHeader();
057:
058: buffer = ByteBuffer.allocateDirect(12 * 1024);
059: buffer.position(buffer.limit());
060: }
061:
062: public IndexedFidReader(String typeName,
063: RecordNumberTracker reader, ReadableByteChannel readChannel)
064: throws IOException {
065: this .reader = reader;
066: this .typeName = typeName + ".";
067: this .readChannel = readChannel;
068: getHeader();
069:
070: buffer = ByteBuffer.allocateDirect(12 * 1024);
071: buffer.position(buffer.limit());
072: }
073:
074: private void getHeader() throws IOException {
075: ByteBuffer buffer = ByteBuffer
076: .allocate(IndexedFidWriter.HEADER_SIZE);
077: ShapefileReader.fill(buffer, readChannel);
078:
079: if (buffer.position() == 0) {
080: done = true;
081: count = 0;
082:
083: return;
084: }
085:
086: buffer.position(0);
087:
088: byte version = buffer.get();
089:
090: if (version != 1) {
091: throw new IOException(
092: "File is not of a compatible version for this reader or file is corrupt.");
093: }
094:
095: this .count = buffer.getLong();
096: this .removes = buffer.getInt();
097: }
098:
099: /**
100: * Returns the number of Fid Entries in the file.
101: *
102: * @return Returns the number of Fid Entries in the file.
103: */
104: public long getCount() {
105: return count;
106: }
107:
108: /**
109: * Returns the number of features that have been removed since the
110: * fid index was regenerated.
111: *
112: * @return Returns the number of features that have been removed since the
113: * fid index was regenerated.
114: */
115: public int getRemoves() {
116: return removes;
117: }
118:
119: /**
120: * Returns the offset to the location in the SHX file that the fid
121: * identifies. This search take logN time.
122: *
123: * @param fid the fid to find.
124: *
125: * @return Returns the record number of the record in the SHX file that the
126: * fid identifies. Will return -1 if the fid was not found.
127: *
128: * @throws IOException
129: * @throws IllegalArgumentException DOCUMENT ME!
130: */
131: public long findFid(String fid) throws IOException {
132: try {
133: long desired = Long.parseLong(fid.substring(fid
134: .lastIndexOf(".") + 1));
135:
136: if ((desired < 0)) {
137: return -1;
138: }
139:
140: if (desired < count) {
141: return search(desired, -1, this .count, desired - 1);
142: } else {
143: return search(desired, -1, this .count, count - 1);
144: }
145: } catch (NumberFormatException e) {
146: LOGGER
147: .warning("Fid is not recognized as a fid for this shapefile: "
148: + typeName);
149: return -1;
150: }
151: }
152:
153: /**
154: * Searches for the desired record.
155: *
156: * @param desired the id of the desired record.
157: * @param minRec the last record that is known to be <em>before</em> the
158: * desired record.
159: * @param maxRec the first record that is known to be <em>after</em> the
160: * desired record.
161: * @param predictedRec the record that is predicted to be the desired
162: * record.
163: *
164: * @return returns the record number of the feature in the shx file.
165: *
166: * @throws IOException
167: */
168: long search(long desired, long minRec, long maxRec,
169: long predictedRec) throws IOException {
170: if (minRec == maxRec) {
171: return -1;
172: }
173:
174: goTo(predictedRec);
175: buffer.limit(IndexedFidWriter.RECORD_SIZE);
176: next();
177: buffer.limit(buffer.capacity());
178: if (currentId == desired) {
179: return currentShxIndex;
180: }
181:
182: if (maxRec - minRec < 10) {
183: return search(desired, minRec + 1, maxRec, minRec + 1);
184: } else {
185: long newOffset = desired - currentId;
186: long newPrediction = predictedRec + newOffset;
187:
188: if (newPrediction <= minRec) {
189: newPrediction = minRec + ((predictedRec - minRec) / 2);
190: }
191:
192: if (newPrediction >= maxRec) {
193: newPrediction = predictedRec
194: + ((maxRec - predictedRec) / 2);
195: }
196:
197: if (newPrediction == predictedRec) {
198: return -1;
199: }
200:
201: if (newPrediction < predictedRec) {
202: return search(desired, minRec, predictedRec,
203: newPrediction);
204: } else {
205: return search(desired, predictedRec, maxRec,
206: newPrediction);
207: }
208: }
209: }
210:
211: /**
212: * move the reader to the recno-th entry in the file.
213: *
214: * @param recno
215: *
216: * @throws IOException
217: */
218: private long lastRecNo = Long.MIN_VALUE;
219: private long bufferStart = Long.MIN_VALUE;
220:
221: public void goTo(long recno) throws IOException {
222: if (readChannel instanceof FileChannel) {
223: long newPosition = IndexedFidWriter.HEADER_SIZE
224: + (recno * IndexedFidWriter.RECORD_SIZE);
225: if (newPosition >= bufferStart + buffer.limit()
226: || newPosition < bufferStart) {
227: FileChannel fc = (FileChannel) readChannel;
228: fc.position(newPosition);
229: buffer.limit(buffer.capacity());
230: buffer.position(buffer.limit());
231: } else {
232: buffer.position((int) (newPosition - bufferStart));
233: }
234: } else {
235: throw new IOException(
236: "Read Channel is not a File Channel so this is not possible.");
237: }
238: }
239:
240: public void close() throws IOException {
241: try {
242: if (buffer != null) {
243: if (buffer instanceof MappedByteBuffer) {
244: NIOUtilities.clean(buffer);
245: }
246: }
247: if (reader != null)
248: reader.close();
249: } finally {
250: readChannel.close();
251: streamLogger.close();
252: }
253: }
254:
255: public boolean hasNext() throws IOException {
256: if (done) {
257: return false;
258: }
259:
260: if (buffer.position() == buffer.limit()) {
261: buffer.position(0);
262:
263: FileChannel fc = (FileChannel) readChannel;
264: bufferStart = fc.position();
265: int read = ShapefileReader.fill(buffer, readChannel);
266:
267: if (read != 0) {
268: buffer.position(0);
269: }
270: }
271:
272: return buffer.remaining() != 0;
273: }
274:
275: public String next() throws IOException {
276: if (reader != null) {
277: goTo(reader.getRecordNumber() - 1);
278: }
279:
280: if (!hasNext()) {
281: throw new NoSuchElementException();
282: }
283:
284: currentId = buffer.getLong();
285: currentShxIndex = buffer.getInt();
286:
287: return typeName + currentId;
288: }
289:
290: /**
291: * Returns the record number of the feature in the shx or shp that
292: * is identified by the the last fid returned by next().
293: *
294: * @return Returns the record number of the feature in the shx or shp that
295: * is identified by the the last fid returned by next().
296: *
297: * @throws NoSuchElementException DOCUMENT ME!
298: */
299: public int currentIndex() {
300: if (currentShxIndex == -1) {
301: throw new NoSuchElementException(
302: "Next must be called before there exists a current element.");
303: }
304:
305: return currentShxIndex;
306: }
307: }
|