/* * * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * * * * For more information: http://www.orientechnologies.com * */ package com.orientechnologies.orient.core.iterator; import com.orientechnologies.common.log.OLogManager; import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal; import com.orientechnologies.orient.core.db.record.ORecordOperation; import com.orientechnologies.orient.core.id.ORID; import com.orientechnologies.orient.core.record.ORecord; import com.orientechnologies.orient.core.storage.OStorage; /** * Iterator class to browse forward and backward the records of a cluster. Once browsed in a direction, the iterator cannot change * it. * * @author Luca Garulli */ public class ORecordIteratorCluster<REC extends ORecord> extends OIdentifiableIterator<REC> { private ORecord currentRecord; public ORecordIteratorCluster(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, final int iClusterId) { this(iDatabase, iLowLevelDatabase, iClusterId, ORID.CLUSTER_POS_INVALID, ORID.CLUSTER_POS_INVALID, false, OStorage.LOCKING_STRATEGY.DEFAULT); } public ORecordIteratorCluster(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, final int iClusterId, final long firstClusterEntry, final long lastClusterEntry) { this(iDatabase, iLowLevelDatabase, iClusterId, firstClusterEntry, lastClusterEntry, false, OStorage.LOCKING_STRATEGY.NONE); } @Deprecated public ORecordIteratorCluster(final ODatabaseDocumentInternal iDatabase, final ODatabaseDocumentInternal iLowLevelDatabase, final int iClusterId, final long firstClusterEntry, final long lastClusterEntry, final boolean iterateThroughTombstones, final OStorage.LOCKING_STRATEGY iLockingStrategy) { super(iDatabase, iLowLevelDatabase, iterateThroughTombstones, iLockingStrategy); if (iClusterId == ORID.CLUSTER_ID_INVALID) throw new IllegalArgumentException("The clusterId is invalid"); checkForSystemClusters(iDatabase, new int[] { iClusterId }); current.setClusterId(iClusterId); final long[] range = database.getStorage().getClusterDataRange(current.getClusterId()); if (firstClusterEntry == ORID.CLUSTER_POS_INVALID) this.firstClusterEntry = range[0]; else this.firstClusterEntry = firstClusterEntry > range[0] ? firstClusterEntry : range[0]; if (lastClusterEntry == ORID.CLUSTER_POS_INVALID) this.lastClusterEntry = range[1]; else this.lastClusterEntry = lastClusterEntry < range[1] ? lastClusterEntry : range[1]; totalAvailableRecords = database.countClusterElements(current.getClusterId(), iterateThroughTombstones); txEntries = iDatabase.getTransaction().getNewRecordEntriesByClusterIds(new int[] { iClusterId }); if (txEntries != null) // ADJUST TOTAL ELEMENT BASED ON CURRENT TRANSACTION'S ENTRIES for (ORecordOperation entry : txEntries) { switch (entry.type) { case ORecordOperation.CREATED: totalAvailableRecords++; break; case ORecordOperation.DELETED: totalAvailableRecords--; break; } } begin(); } @Override public boolean hasPrevious() { checkDirection(false); updateRangesOnLiveUpdate(); if (currentRecord != null) { return true; } if (limit > -1 && browsedRecords >= limit) // LIMIT REACHED return false; boolean thereAreRecordsToBrowse = getCurrentEntry() > firstClusterEntry; if (thereAreRecordsToBrowse) { ORecord record = getRecord(); currentRecord = readCurrentRecord(record, -1); } return currentRecord != null; } public boolean hasNext() { checkDirection(true); if (Thread.interrupted()) // INTERRUPTED return false; updateRangesOnLiveUpdate(); if (currentRecord != null) { return true; } if (limit > -1 && browsedRecords >= limit) // LIMIT REACHED return false; if (browsedRecords >= totalAvailableRecords) return false; if (!(current.getClusterPosition() < ORID.CLUSTER_POS_INVALID) && getCurrentEntry() < lastClusterEntry) { ORecord record = getRecord(); try { currentRecord = readCurrentRecord(record, +1); } catch (Exception e) { OLogManager.instance().error(this, "Error during read of record", e); currentRecord = null; } if (currentRecord != null) return true; } // CHECK IN TX IF ANY if (txEntries != null) return txEntries.size() - (currentTxEntryPosition + 1) > 0; return false; } /** * Return the element at the current position and move backward the cursor to the previous position available. * * @return the previous record found, otherwise the NoSuchElementException exception is thrown when no more records are found. */ @SuppressWarnings("unchecked") @Override public REC previous() { checkDirection(false); if (currentRecord != null) { try { return (REC) currentRecord; } finally { currentRecord = null; } } // ITERATE UNTIL THE PREVIOUS GOOD RECORD while (hasPrevious()) { try { return (REC) currentRecord; } finally { currentRecord = null; } } return null; } /** * Return the element at the current position and move forward the cursor to the next position available. * * @return the next record found, otherwise the NoSuchElementException exception is thrown when no more records are found. */ @SuppressWarnings("unchecked") public REC next() { checkDirection(true); ORecord record; // ITERATE UNTIL THE NEXT GOOD RECORD while (hasNext()) { // FOUND if (currentRecord != null) { try { return (REC) currentRecord; } finally { currentRecord = null; } } record = getTransactionEntry(); if (record != null) return (REC) record; } return null; } /** * Move the iterator to the begin of the range. If no range was specified move to the first record of the cluster. * * @return The object itself */ @Override public ORecordIteratorCluster<REC> begin() { browsedRecords = 0; updateRangesOnLiveUpdate(); resetCurrentPosition(); currentRecord = readCurrentRecord(getRecord(), +1); return this; } /** * Move the iterator to the end of the range. If no range was specified move to the last record of the cluster. * * @return The object itself */ @Override public ORecordIteratorCluster<REC> last() { browsedRecords = 0; updateRangesOnLiveUpdate(); resetCurrentPosition(); currentRecord = readCurrentRecord(getRecord(), -1); return this; } /** * Tell to the iterator that the upper limit must be checked at every cycle. Useful when concurrent deletes or additions change * the size of the cluster while you're browsing it. Default is false. * * @param iLiveUpdated * True to activate it, otherwise false (default) * @see #isLiveUpdated() */ @Override public ORecordIteratorCluster<REC> setLiveUpdated(boolean iLiveUpdated) { super.setLiveUpdated(iLiveUpdated); // SET THE RANGE LIMITS if (iLiveUpdated) { firstClusterEntry = 0L; lastClusterEntry = Long.MAX_VALUE; } else { final long[] range = database.getStorage().getClusterDataRange(current.getClusterId()); firstClusterEntry = range[0]; lastClusterEntry = range[1]; } totalAvailableRecords = database.countClusterElements(current.getClusterId(), isIterateThroughTombstones()); return this; } private void updateRangesOnLiveUpdate() { if (liveUpdated) { final long[] range = database.getStorage().getClusterDataRange(current.getClusterId()); firstClusterEntry = range[0]; lastClusterEntry = range[1]; } } }