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.io.UncheckedIOException;
21 import java.lang.System.Logger;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.HashSet;
25 import java.util.Iterator;
26 import java.util.Map;
27 import java.util.Set;
28
29 import com.healthmarketscience.jackcess.Index;
30 import com.healthmarketscience.jackcess.IndexCursor;
31 import com.healthmarketscience.jackcess.Row;
32 import com.healthmarketscience.jackcess.impl.TableImpl.RowState;
33 import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher;
34 import com.healthmarketscience.jackcess.util.ColumnMatcher;
35 import com.healthmarketscience.jackcess.util.EntryIterableBuilder;
36 import com.healthmarketscience.jackcess.util.SimpleColumnMatcher;
37
38
39
40
41
42
43 public class IndexCursorImpl extends CursorImpl implements IndexCursor
44 {
45 private static final Logger LOG = System.getLogger(IndexCursorImpl.class.getName());
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().isValid()) {
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<>();
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.log(Logger.Level.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.log(Logger.Level.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 return findMatchingRowInRun(
264 rowValues, moveForward, columnMatcher,
265 () -> currentRowMatchesImpl(columnPattern, valuePattern,
266 columnMatcher));
267 }
268
269
270
271
272
273
274
275
276
277
278 protected boolean findFirstRowByEntryImpl(Object[] rowValues,
279 boolean requireMatch,
280 ColumnMatcher columnMatcher)
281 throws IOException
282 {
283 if(!findPotentialRow(rowValues, requireMatch)) {
284 return false;
285 } else if(!requireMatch) {
286
287 return true;
288 }
289
290 return findMatchingRowInRun(rowValues, MOVE_FORWARD, columnMatcher,
291 MATCHES_ENTRY);
292 }
293
294 @Override
295 protected boolean findAnotherRowImpl(
296 Map<String,?> rowPattern, boolean moveForward,
297 ColumnMatcher columnMatcher, Object searchInfo)
298 throws IOException
299 {
300 Object[] rowValues = (Object[])searchInfo;
301
302 if((rowValues == null) || !isAtBeginning(moveForward)) {
303
304
305 return super.findAnotherRowImpl(rowPattern, moveForward, columnMatcher,
306 rowValues);
307 }
308
309
310 if(!findPotentialRow(rowValues, true)) {
311
312 return false;
313 }
314
315
316 boolean exactColumnMatch = rowPattern.keySet().equals(
317 getIndexEntryPattern());
318
319
320
321
322 return findMatchingRowInRun(
323 rowValues, moveForward, columnMatcher,
324 () -> (exactColumnMatch ||
325 currentRowMatchesImpl(rowPattern, columnMatcher)));
326 }
327
328
329
330
331
332 private interface RowTest
333 {
334 public boolean matches() throws IOException;
335 }
336
337
338 private static final RowTest MATCHES_ENTRY = () -> true;
339
340
341
342
343
344
345
346
347 private boolean findMatchingRowInRun(Object[] rowValues, boolean moveForward,
348 ColumnMatcher columnMatcher,
349 RowTest test)
350 throws IOException
351 {
352 byte[] runBytes = currentEntryBytes();
353
354 do {
355
356 if(currentRowMatchesEntryImpl(rowValues, columnMatcher) &&
357 test.matches()) {
358 return true;
359 }
360
361 } while(moveToAnotherRow(moveForward) &&
362 couldStillMatch(rowValues, columnMatcher, runBytes));
363
364 return false;
365 }
366
367
368
369
370 private boolean couldStillMatch(Object[] rowValues,
371 ColumnMatcher columnMatcher,
372 byte[] runBytes)
373 throws IOException
374 {
375 if(currentRowMatchesEntryImpl(rowValues, columnMatcher)) {
376 return true;
377 }
378
379
380
381
382
383
384 byte[] entryBytes = currentEntryBytes();
385 return ((runBytes != null) && Arrays.equals(runBytes, entryBytes));
386 }
387
388
389
390
391
392 private byte[] currentEntryBytes() {
393 IndexData.Entry entry = ((IndexPosition)_curPos).getEntry();
394 return (entry.isValid() ? entry.getEntryBytes() : null);
395 }
396
397 private boolean currentRowMatchesEntryImpl(Object[] rowValues,
398 ColumnMatcher columnMatcher)
399 throws IOException
400 {
401
402 Row row = getCurrentRow(getIndexEntryPattern());
403
404 for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
405
406 Object patValue = rowValues[col.getColumnIndex()];
407
408 if((patValue == IndexData.MIN_VALUE) ||
409 (patValue == IndexData.MAX_VALUE)) {
410
411 return true;
412 }
413
414 String columnName = col.getName();
415 Object rowValue = row.get(columnName);
416 if(!columnMatcher.matches(getTable(), columnName, patValue, rowValue)) {
417 return false;
418 }
419 }
420
421 return true;
422 }
423
424 private boolean findPotentialRow(Object[] rowValues, boolean requireMatch)
425 throws IOException
426 {
427 _entryCursor.beforeEntry(rowValues);
428 IndexData.Entry startEntry = _entryCursor.getNextEntry();
429 if(requireMatch && !startEntry.getRowId().isValid()) {
430
431 return false;
432 }
433
434 restorePosition(new IndexPosition(startEntry));
435 return true;
436 }
437
438 @Override
439 protected Object prepareSearchInfo(ColumnImpl columnPattern, Object valuePattern)
440 {
441
442 return _entryCursor.getIndexData().constructPartialIndexRow(
443 IndexData.MIN_VALUE, columnPattern.getName(), valuePattern);
444 }
445
446 @Override
447 protected Object prepareSearchInfo(Map<String,?> rowPattern)
448 {
449
450 return _entryCursor.getIndexData().constructPartialIndexRow(
451 IndexData.MIN_VALUE, rowPattern);
452 }
453
454 @Override
455 protected boolean keepSearching(ColumnMatcher columnMatcher,
456 Object searchInfo)
457 throws IOException
458 {
459 if(searchInfo instanceof Object[]) {
460
461
462
463
464 return currentRowMatchesEntryImpl((Object[])searchInfo, columnMatcher);
465 }
466
467 return true;
468 }
469
470 private Object[] toRowValues(Object[] entryValues)
471 {
472 return _entryCursor.getIndexData().constructPartialIndexRowFromEntry(
473 IndexData.MIN_VALUE, entryValues);
474 }
475
476 @Override
477 protected PositionImpl findAnotherPosition(
478 RowState rowState, PositionImpl curPos, boolean moveForward)
479 throws IOException
480 {
481 IndexDirHandler handler = getDirHandler(moveForward);
482 IndexPosition endPos = (IndexPosition)handler.getEndPosition();
483 IndexData.Entry entry = handler.getAnotherEntry();
484 return ((!entry.equals(endPos.getEntry())) ?
485 new IndexPosition(entry) : endPos);
486 }
487
488 @Override
489 protected ColumnMatcher getDefaultColumnMatcher() {
490 if(getIndex().isUnique()) {
491
492
493 return CaseInsensitiveColumnMatcher.INSTANCE;
494 }
495 return SimpleColumnMatcher.INSTANCE;
496 }
497
498
499
500
501
502 private abstract class IndexDirHandler extends DirHandler {
503 public abstract IndexData.Entry getAnotherEntry()
504 throws IOException;
505 }
506
507
508
509
510 private final class ForwardIndexDirHandler extends IndexDirHandler {
511 @Override
512 public PositionImpl getBeginningPosition() {
513 return getFirstPosition();
514 }
515 @Override
516 public PositionImpl getEndPosition() {
517 return getLastPosition();
518 }
519 @Override
520 public IndexData.Entry getAnotherEntry() throws IOException {
521 return _entryCursor.getNextEntry();
522 }
523 }
524
525
526
527
528 private final class ReverseIndexDirHandler extends IndexDirHandler {
529 @Override
530 public PositionImpl getBeginningPosition() {
531 return getLastPosition();
532 }
533 @Override
534 public PositionImpl getEndPosition() {
535 return getFirstPosition();
536 }
537 @Override
538 public IndexData.Entry getAnotherEntry() throws IOException {
539 return _entryCursor.getPreviousEntry();
540 }
541 }
542
543
544
545
546 private static final class IndexPosition extends PositionImpl
547 {
548 private final IndexData.Entry _entry;
549
550 private IndexPosition(IndexData.Entry entry) {
551 _entry = entry;
552 }
553
554 @Override
555 public RowIdImpl getRowId() {
556 return getEntry().getRowId();
557 }
558
559 public IndexData.Entry getEntry() {
560 return _entry;
561 }
562
563 @Override
564 protected boolean equalsImpl(Object o) {
565 return getEntry().equals(((IndexPosition)o).getEntry());
566 }
567
568 @Override
569 public String toString() {
570 return "Entry = " + getEntry();
571 }
572 }
573
574
575
576
577 private final class EntryIterator extends BaseIterator
578 {
579 private final Object[] _rowValues;
580
581 private EntryIterator(Collection<String> columnNames, Object[] rowValues,
582 ColumnMatcher columnMatcher)
583 {
584 super(columnNames, false, MOVE_FORWARD, columnMatcher);
585 _rowValues = rowValues;
586 try {
587 _hasNext = findFirstRowByEntryImpl(rowValues, true, _columnMatcher);
588 _validRow = _hasNext;
589 } catch(IOException e) {
590 throw new UncheckedIOException(e);
591 }
592 }
593
594 @Override
595 protected boolean findNext() throws IOException {
596
597
598 return (moveToNextRow() &&
599 findMatchingRowInRun(_rowValues, MOVE_FORWARD, _colMatcher,
600 MATCHES_ENTRY));
601 }
602 }
603
604 }