1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl;
18
19 import java.io.IOException;
20 import java.util.Collection;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.Map;
24 import java.util.Set;
25
26 import com.healthmarketscience.jackcess.Index;
27 import com.healthmarketscience.jackcess.IndexCursor;
28 import com.healthmarketscience.jackcess.Row;
29 import com.healthmarketscience.jackcess.RuntimeIOException;
30 import com.healthmarketscience.jackcess.impl.TableImpl.RowState;
31 import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher;
32 import com.healthmarketscience.jackcess.util.ColumnMatcher;
33 import com.healthmarketscience.jackcess.util.EntryIterableBuilder;
34 import com.healthmarketscience.jackcess.util.SimpleColumnMatcher;
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37
38
39
40
41
42
43 public class IndexCursorImpl extends CursorImpl implements IndexCursor
44 {
45 private static final Log LOG = LogFactory.getLog(IndexCursorImpl.class);
46
47
48 private final IndexDirHandler _forwardDirHandler =
49 new ForwardIndexDirHandler();
50
51 private final IndexDirHandler _reverseDirHandler =
52 new ReverseIndexDirHandler();
53
54 private final IndexImpl _index;
55
56 private final IndexData.EntryCursor _entryCursor;
57
58 private Set<String> _indexEntryPattern;
59
60 private IndexCursorImpl(TableImpl table, IndexImpl index,
61 IndexData.EntryCursor entryCursor)
62 throws IOException
63 {
64 super(new IdImpl(table, index), table,
65 new IndexPosition(entryCursor.getFirstEntry()),
66 new IndexPosition(entryCursor.getLastEntry()));
67 _index = index;
68 _index.initialize();
69 _entryCursor = entryCursor;
70 }
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 public static IndexCursorImpl createCursor(TableImpl table, IndexImpl index,
91 Object[] startRow,
92 boolean startInclusive,
93 Object[] endRow,
94 boolean endInclusive)
95 throws IOException
96 {
97 if(table != index.getTable()) {
98 throw new IllegalArgumentException(
99 "Given index is not for given table: " + index + ", " + table);
100 }
101 if(index.getIndexData().getUnsupportedReason() != null) {
102 throw new IllegalArgumentException(
103 "Given index " + index +
104 " is not usable for indexed lookups due to " +
105 index.getIndexData().getUnsupportedReason());
106 }
107 IndexCursorImpl/IndexCursorImpl.html#IndexCursorImpl">IndexCursorImpl cursor = new IndexCursorImpl(
108 table, index, index.cursor(startRow, startInclusive,
109 endRow, endInclusive));
110
111 cursor.setColumnMatcher(null);
112 return cursor;
113 }
114
115 private Set<String> getIndexEntryPattern()
116 {
117 if(_indexEntryPattern == null) {
118
119 _indexEntryPattern = new HashSet<String>();
120 for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
121 _indexEntryPattern.add(col.getName());
122 }
123 }
124 return _indexEntryPattern;
125 }
126
127 @Override
128 public IndexImpl getIndex() {
129 return _index;
130 }
131
132 @Override
133 public Row findRowByEntry(Object... entryValues)
134 throws IOException
135 {
136 if(findFirstRowByEntry(entryValues)) {
137 return getCurrentRow();
138 }
139 return null;
140 }
141
142 @Override
143 public boolean findFirstRowByEntry(Object... entryValues)
144 throws IOException
145 {
146 PositionImpl curPos = _curPos;
147 PositionImpl prevPos = _prevPos;
148 boolean found = false;
149 try {
150 found = findFirstRowByEntryImpl(toRowValues(entryValues), true,
151 _columnMatcher);
152 return found;
153 } finally {
154 if(!found) {
155 try {
156 restorePosition(curPos, prevPos);
157 } catch(IOException e) {
158 LOG.error("Failed restoring position", e);
159 }
160 }
161 }
162 }
163
164 @Override
165 public void findClosestRowByEntry(Object... entryValues)
166 throws IOException
167 {
168 PositionImpl curPos = _curPos;
169 PositionImpl prevPos = _prevPos;
170 boolean found = false;
171 try {
172 findFirstRowByEntryImpl(toRowValues(entryValues), false,
173 _columnMatcher);
174 found = true;
175 } finally {
176 if(!found) {
177 try {
178 restorePosition(curPos, prevPos);
179 } catch(IOException e) {
180 LOG.error("Failed restoring position", e);
181 }
182 }
183 }
184 }
185
186 @Override
187 public boolean currentRowMatchesEntry(Object... entryValues)
188 throws IOException
189 {
190 return currentRowMatchesEntryImpl(toRowValues(entryValues), _columnMatcher);
191 }
192
193 @Override
194 public EntryIterableBuilder newEntryIterable(Object... entryValues) {
195 return new EntryIterableBuilder(this, entryValues);
196 }
197
198 public Iterator<Row> entryIterator(EntryIterableBuilder iterBuilder) {
199 return new EntryIterator(iterBuilder.getColumnNames(),
200 toRowValues(iterBuilder.getEntryValues()),
201 iterBuilder.getColumnMatcher());
202 }
203
204 @Override
205 protected IndexDirHandler getDirHandler(boolean moveForward) {
206 return (moveForward ? _forwardDirHandler : _reverseDirHandler);
207 }
208
209 @Override
210 protected boolean isUpToDate() {
211 return(super.isUpToDate() && _entryCursor.isUpToDate());
212 }
213
214 @Override
215 protected void reset(boolean moveForward) {
216 _entryCursor.reset(moveForward);
217 super.reset(moveForward);
218 }
219
220 @Override
221 protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos)
222 throws IOException
223 {
224 if(!(curPos instanceof IndexPosition) ||
225 !(prevPos instanceof IndexPosition)) {
226 throw new IllegalArgumentException(
227 "Restored positions must be index positions");
228 }
229 _entryCursor.restorePosition(((IndexPosition)curPos).getEntry(),
230 ((IndexPosition)prevPos).getEntry());
231 super.restorePositionImpl(curPos, prevPos);
232 }
233
234 @Override
235 protected PositionImpl getRowPosition(RowIdImpl rowId) throws IOException
236 {
237
238 Row row = getTable().getRow(getRowState(), rowId, getIndexEntryPattern());
239 _entryCursor.beforeEntry(getTable().asRow(row));
240 return new IndexPosition(_entryCursor.getNextEntry());
241 }
242
243 @Override
244 protected boolean findAnotherRowImpl(
245 ColumnImpl columnPattern, Object valuePattern, boolean moveForward,
246 ColumnMatcher columnMatcher, Object searchInfo)
247 throws IOException
248 {
249 Object[] rowValues = (Object[])searchInfo;
250
251 if((rowValues == null) || !isAtBeginning(moveForward)) {
252
253
254 return super.findAnotherRowImpl(columnPattern, valuePattern, moveForward,
255 columnMatcher, rowValues);
256 }
257
258
259 if(!findPotentialRow(rowValues, true)) {
260 return false;
261 }
262
263
264 return currentRowMatchesImpl(columnPattern, valuePattern, columnMatcher);
265 }
266
267
268
269
270
271
272
273
274
275
276 protected boolean findFirstRowByEntryImpl(Object[] rowValues,
277 boolean requireMatch,
278 ColumnMatcher columnMatcher)
279 throws IOException
280 {
281 if(!findPotentialRow(rowValues, requireMatch)) {
282 return false;
283 } else if(!requireMatch) {
284
285 return true;
286 }
287
288 return currentRowMatchesEntryImpl(rowValues, columnMatcher);
289 }
290
291 @Override
292 protected boolean findAnotherRowImpl(
293 Map<String,?> rowPattern, boolean moveForward,
294 ColumnMatcher columnMatcher, Object searchInfo)
295 throws IOException
296 {
297 Object[] rowValues = (Object[])searchInfo;
298
299 if((rowValues == null) || !isAtBeginning(moveForward)) {
300
301
302 return super.findAnotherRowImpl(rowPattern, moveForward, columnMatcher,
303 rowValues);
304 }
305
306
307 if(!findPotentialRow(rowValues, true)) {
308
309 return false;
310 }
311
312
313 boolean exactColumnMatch = rowPattern.keySet().equals(
314 getIndexEntryPattern());
315
316
317
318
319 do {
320
321 if(!currentRowMatchesEntryImpl(rowValues, columnMatcher)) {
322
323 break;
324 }
325
326
327
328
329 if(exactColumnMatch || currentRowMatchesImpl(rowPattern, columnMatcher)) {
330
331 return true;
332 }
333
334 } while(moveToAnotherRow(moveForward));
335
336
337 return false;
338 }
339
340 private boolean currentRowMatchesEntryImpl(Object[] rowValues,
341 ColumnMatcher columnMatcher)
342 throws IOException
343 {
344
345 Row row = getCurrentRow(getIndexEntryPattern());
346
347 for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
348
349 Object patValue = rowValues[col.getColumnIndex()];
350
351 if((patValue == IndexData.MIN_VALUE) ||
352 (patValue == IndexData.MAX_VALUE)) {
353
354 return true;
355 }
356
357 String columnName = col.getName();
358 Object rowValue = row.get(columnName);
359 if(!columnMatcher.matches(getTable(), columnName, patValue, rowValue)) {
360 return false;
361 }
362 }
363
364 return true;
365 }
366
367 private boolean findPotentialRow(Object[] rowValues, boolean requireMatch)
368 throws IOException
369 {
370 _entryCursor.beforeEntry(rowValues);
371 IndexData.Entry startEntry = _entryCursor.getNextEntry();
372 if(requireMatch && !startEntry.getRowId().isValid()) {
373
374 return false;
375 }
376
377 restorePosition(new IndexPosition(startEntry));
378 return true;
379 }
380
381 @Override
382 protected Object prepareSearchInfo(ColumnImpl columnPattern, Object valuePattern)
383 {
384
385 return _entryCursor.getIndexData().constructPartialIndexRow(
386 IndexData.MIN_VALUE, columnPattern.getName(), valuePattern);
387 }
388
389 @Override
390 protected Object prepareSearchInfo(Map<String,?> rowPattern)
391 {
392
393 return _entryCursor.getIndexData().constructPartialIndexRow(
394 IndexData.MIN_VALUE, rowPattern);
395 }
396
397 @Override
398 protected boolean keepSearching(ColumnMatcher columnMatcher,
399 Object searchInfo)
400 throws IOException
401 {
402 if(searchInfo instanceof Object[]) {
403
404
405
406
407 return currentRowMatchesEntryImpl((Object[])searchInfo, columnMatcher);
408 }
409
410 return true;
411 }
412
413 private Object[] toRowValues(Object[] entryValues)
414 {
415 return _entryCursor.getIndexData().constructPartialIndexRowFromEntry(
416 IndexData.MIN_VALUE, entryValues);
417 }
418
419 @Override
420 protected PositionImpl findAnotherPosition(
421 RowState rowState, PositionImpl curPos, boolean moveForward)
422 throws IOException
423 {
424 IndexDirHandler handler = getDirHandler(moveForward);
425 IndexPosition endPos = (IndexPosition)handler.getEndPosition();
426 IndexData.Entry entry = handler.getAnotherEntry();
427 return ((!entry.equals(endPos.getEntry())) ?
428 new IndexPosition(entry) : endPos);
429 }
430
431 @Override
432 protected ColumnMatcher getDefaultColumnMatcher() {
433 if(getIndex().isUnique()) {
434
435
436 return CaseInsensitiveColumnMatcher.INSTANCE;
437 }
438 return SimpleColumnMatcher.INSTANCE;
439 }
440
441
442
443
444
445 private abstract class IndexDirHandler extends DirHandler {
446 public abstract IndexData.Entry getAnotherEntry()
447 throws IOException;
448 }
449
450
451
452
453 private final class ForwardIndexDirHandler extends IndexDirHandler {
454 @Override
455 public PositionImpl getBeginningPosition() {
456 return getFirstPosition();
457 }
458 @Override
459 public PositionImpl getEndPosition() {
460 return getLastPosition();
461 }
462 @Override
463 public IndexData.Entry getAnotherEntry() throws IOException {
464 return _entryCursor.getNextEntry();
465 }
466 }
467
468
469
470
471 private final class ReverseIndexDirHandler extends IndexDirHandler {
472 @Override
473 public PositionImpl getBeginningPosition() {
474 return getLastPosition();
475 }
476 @Override
477 public PositionImpl getEndPosition() {
478 return getFirstPosition();
479 }
480 @Override
481 public IndexData.Entry getAnotherEntry() throws IOException {
482 return _entryCursor.getPreviousEntry();
483 }
484 }
485
486
487
488
489 private static final class IndexPosition extends PositionImpl
490 {
491 private final IndexData.Entry _entry;
492
493 private IndexPosition(IndexData.Entry entry) {
494 _entry = entry;
495 }
496
497 @Override
498 public RowIdImpl getRowId() {
499 return getEntry().getRowId();
500 }
501
502 public IndexData.Entry getEntry() {
503 return _entry;
504 }
505
506 @Override
507 protected boolean equalsImpl(Object o) {
508 return getEntry().equals(((IndexPosition)o).getEntry());
509 }
510
511 @Override
512 public String toString() {
513 return "Entry = " + getEntry();
514 }
515 }
516
517
518
519
520 private final class EntryIterator extends BaseIterator
521 {
522 private final Object[] _rowValues;
523
524 private EntryIterator(Collection<String> columnNames, Object[] rowValues,
525 ColumnMatcher columnMatcher)
526 {
527 super(columnNames, false, MOVE_FORWARD, columnMatcher);
528 _rowValues = rowValues;
529 try {
530 _hasNext = findFirstRowByEntryImpl(rowValues, true, _columnMatcher);
531 _validRow = _hasNext;
532 } catch(IOException e) {
533 throw new RuntimeIOException(e);
534 }
535 }
536
537 @Override
538 protected boolean findNext() throws IOException {
539 return (moveToNextRow() &&
540 currentRowMatchesEntryImpl(_rowValues, _colMatcher));
541 }
542 }
543
544 }