View Javadoc
1   /*
2   Copyright (c) 2013 James Ahlborn
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess.impl;
18  
19  import java.io.IOException;
20  
21  import com.healthmarketscience.jackcess.impl.TableImpl.RowState;
22  
23  
24  /**
25   * Simple un-indexed cursor.
26   *
27   * @author James Ahlborn
28   */
29  public class TableScanCursor extends CursorImpl 
30  {
31    /** first position for the TableScanCursor */
32    private static final ScanPosition FIRST_SCAN_POSITION =
33      new ScanPosition(RowIdImpl.FIRST_ROW_ID);
34    /** last position for the TableScanCursor */
35    private static final ScanPosition LAST_SCAN_POSITION =
36      new ScanPosition(RowIdImpl.LAST_ROW_ID);
37  
38  
39    /** ScanDirHandler for forward traversal */
40    private final ScanDirHandler _forwardDirHandler =
41      new ForwardScanDirHandler();
42    /** ScanDirHandler for backward traversal */
43    private final ScanDirHandler _reverseDirHandler =
44      new ReverseScanDirHandler();
45    /** Cursor over the pages that this table owns */
46    private final UsageMap.PageCursor _ownedPagesCursor;
47      
48    public TableScanCursor(TableImpl table) {
49      super(new IdImpl(table, null), table,
50            FIRST_SCAN_POSITION, LAST_SCAN_POSITION);
51      _ownedPagesCursor = table.getOwnedPagesCursor();
52    }
53  
54    @Override
55    protected ScanDirHandler getDirHandler(boolean moveForward) {
56      return (moveForward ? _forwardDirHandler : _reverseDirHandler);
57    }
58  
59    @Override
60    protected boolean isUpToDate() {
61      return(super.isUpToDate() && _ownedPagesCursor.isUpToDate());
62    }
63      
64    @Override
65    protected void reset(boolean moveForward) {
66      _ownedPagesCursor.reset(moveForward);
67      super.reset(moveForward);
68    }
69  
70    @Override
71    protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos)
72      throws IOException
73    {
74      if(!(curPos instanceof ScanPosition) ||
75         !(prevPos instanceof ScanPosition)) {
76        throw new IllegalArgumentException(
77            "Restored positions must be scan positions");
78      }
79      _ownedPagesCursor.restorePosition(curPos.getRowId().getPageNumber(),
80                                        prevPos.getRowId().getPageNumber());
81      super.restorePositionImpl(curPos, prevPos);
82    }
83  
84    @Override
85    protected PositionImpl getRowPosition(RowIdImpl rowId) throws IOException
86    {
87      return new ScanPosition(rowId);
88    }
89  
90    @Override
91    protected PositionImpl findAnotherPosition(
92        RowState rowState, PositionImpl curPos, boolean moveForward)
93      throws IOException
94    {
95      ScanDirHandler handler = getDirHandler(moveForward);
96        
97      // figure out how many rows are left on this page so we can find the
98      // next row
99      RowIdImpl curRowId = curPos.getRowId();
100     TableImpl.positionAtRowHeader(rowState, curRowId);
101     int currentRowNumber = curRowId.getRowNumber();
102     
103     // loop until we find the next valid row or run out of pages
104     while(true) {
105 
106       currentRowNumber = handler.getAnotherRowNumber(currentRowNumber);
107       curRowId = new RowIdImpl(curRowId.getPageNumber(), currentRowNumber);
108       TableImpl.positionAtRowHeader(rowState, curRowId);
109         
110       if(!rowState.isValid()) {
111           
112         // load next page
113         curRowId = new RowIdImpl(handler.getAnotherPageNumber(),
114                                  RowIdImpl.INVALID_ROW_NUMBER);
115         TableImpl.positionAtRowHeader(rowState, curRowId);
116           
117         if(!rowState.isHeaderPageNumberValid()) {
118           //No more owned pages.  No more rows.
119           return handler.getEndPosition();
120         }
121 
122         // update row count and initial row number
123         currentRowNumber = handler.getInitialRowNumber(
124             rowState.getRowsOnHeaderPage());
125 
126       } else if(!rowState.isDeleted()) {
127           
128         // we found a valid, non-deleted row, return it
129         return new ScanPosition(curRowId);
130       }
131         
132     }
133   }
134 
135   /**
136    * Handles moving the table scan cursor in a given direction.  Separates
137    * cursor logic from value storage.
138    */
139   private abstract class ScanDirHandler extends DirHandler {
140     public abstract int getAnotherRowNumber(int curRowNumber);
141     public abstract int getAnotherPageNumber();
142     public abstract int getInitialRowNumber(int rowsOnPage);
143   }
144     
145   /**
146    * Handles moving the table scan cursor forward.
147    */
148   private final class ForwardScanDirHandler extends ScanDirHandler {
149     @Override
150     public PositionImpl getBeginningPosition() {
151       return getFirstPosition();
152     }
153     @Override
154     public PositionImpl getEndPosition() {
155       return getLastPosition();
156     }
157     @Override
158     public int getAnotherRowNumber(int curRowNumber) {
159       return curRowNumber + 1;
160     }
161     @Override
162     public int getAnotherPageNumber() {
163       return _ownedPagesCursor.getNextPage();
164     }
165     @Override
166     public int getInitialRowNumber(int rowsOnPage) {
167       return -1;
168     }
169   }
170     
171   /**
172    * Handles moving the table scan cursor backward.
173    */
174   private final class ReverseScanDirHandler extends ScanDirHandler {
175     @Override
176     public PositionImpl getBeginningPosition() {
177       return getLastPosition();
178     }
179     @Override
180     public PositionImpl getEndPosition() {
181       return getFirstPosition();
182     }
183     @Override
184     public int getAnotherRowNumber(int curRowNumber) {
185       return curRowNumber - 1;
186     }
187     @Override
188     public int getAnotherPageNumber() {
189       return _ownedPagesCursor.getPreviousPage();
190     }
191     @Override
192     public int getInitialRowNumber(int rowsOnPage) {
193       return rowsOnPage;
194     }
195   }    
196 
197   /**
198    * Value object which maintains the current position of a TableScanCursor.
199    */
200   private static final class ScanPosition extends PositionImpl
201   {
202     private final RowIdImpl _rowId;
203 
204     private ScanPosition(RowIdImpl rowId) {
205       _rowId = rowId;
206     }
207 
208     @Override
209     public RowIdImpl getRowId() {
210       return _rowId;
211     }
212 
213     @Override
214     protected boolean equalsImpl(Object o) {
215       return getRowId().equals(((ScanPosition)o).getRowId());
216     }
217     
218     @Override
219     public String toString() {
220       return "RowId = " + getRowId();
221     }
222   }
223 }