/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.jackrabbit.rmi.client.iterator;
import java.rmi.RemoteException;
import java.util.NoSuchElementException;
import javax.jcr.RangeIterator;
import org.apache.jackrabbit.rmi.client.ClientObject;
import org.apache.jackrabbit.rmi.client.LocalAdapterFactory;
import org.apache.jackrabbit.rmi.client.RemoteRuntimeException;
import org.apache.jackrabbit.rmi.remote.RemoteIterator;
/**
* A buffering local adapter for the JCR-RMI {@link RemoteIterator}
* interface. This class makes the remote iterator locally available
* using the JCR {@link RangeIterator} interface. The element arrays
* returned by the remote iterator are buffered locally.
* <p>
* See the subclasses for type-specific versions of this abstract class.
*/
public abstract class ClientIterator extends ClientObject
implements RangeIterator {
/** The adapted remote iterator. */
private final RemoteIterator remote;
/**
* The cached number of elements in the iterator, -1 if the iterator
* size is unknown, or -2 if the size has not been retrieved from the
* remote iterator.
*/
private long size;
/** The position of the buffer within the iteration. */
private long positionOfBuffer;
/** The position within the buffer of the iteration. */
private int positionInBuffer;
/**
* The element buffer. Set to <code>null</code> when the end of the
* iteration has been reached.
*/
private Object[] buffer;
/**
* Creates a local adapter for the given remote iterator. The element
* buffer is initially empty.
*
* @param remote remote iterator
* @param factory local adapter factory
*/
public ClientIterator(RemoteIterator remote, LocalAdapterFactory factory) {
super(factory);
this.remote = remote;
this.size = -2;
this.positionOfBuffer = 0;
this.positionInBuffer = 0;
this.buffer = new Object[0];
}
/**
* Returns the current position within the iterator.
*
* @return current position
* @see RangeIterator#getPosition()
*/
public long getPosition() {
return positionOfBuffer + positionInBuffer;
}
/**
* Returns the size (the total number of elements) of this iteration.
* Returns <code>-1</code> if the size is unknown.
* <p>
* To minimize the number of remote method calls, the size is retrieved
* when this method is first called and cached for subsequent invocations.
*
* @return number of elements in the iteration, or <code>-1</code>
* @throws RemoteRuntimeException on RMI errors
* @see RangeIterator#getSize()
*/
public long getSize() throws RemoteRuntimeException {
if (size == -2) {
try {
size = remote.getSize();
} catch (RemoteException e) {
throw new RemoteRuntimeException(e);
}
}
return size;
}
/**
* Skips the given number of elements in this iteration.
* <p>
* The elements in the local element buffer are skipped first, and
* a remote skip method call is made only if more elements are being
* skipped than remain in the local buffer.
*
* @param skipNum the number of elements to skip
* @throws NoSuchElementException if skipped past the last element
* @throws RemoteRuntimeException on RMI errors
* @see RangeIterator#skip(long)
*/
public void skip(long skipNum)
throws NoSuchElementException, RemoteRuntimeException {
if (skipNum < 0) {
throw new IllegalArgumentException("Negative skip is not allowed");
} else if (buffer == null && skipNum > 0) {
throw new NoSuchElementException("Skipped past the last element");
} else if (positionInBuffer + skipNum <= buffer.length) {
positionInBuffer += skipNum;
} else {
try {
skipNum -= buffer.length - positionInBuffer;
remote.skip(skipNum);
positionInBuffer = buffer.length;
positionOfBuffer += skipNum;
} catch (RemoteException e) {
throw new RemoteRuntimeException(e);
} catch (NoSuchElementException e) {
buffer = null; // End of iterator reached
throw e;
}
}
}
/**
* Advances the element buffer if there are no more elements in it. The
* element buffer is set to <code>null</code> if the end of the iteration
* has been reached.
*
* @throws RemoteException on RMI errors
*/
private void advance() throws RemoteException {
if (buffer != null && positionInBuffer == buffer.length) {
positionOfBuffer += buffer.length;
positionInBuffer = 0;
buffer = remote.nextObjects();
if (buffer == null) {
size = positionOfBuffer;
}
}
}
/**
* Checks if there are more elements in this iteration.
*
* @return <code>true</code> if there are more elements,
* <code>false</code> otherwise
* @throws RemoteRuntimeException on RMI errors
*/
public boolean hasNext() throws RemoteRuntimeException {
try {
advance();
return buffer != null;
} catch (RemoteException e) {
throw new RemoteRuntimeException(e);
}
}
/**
* Returns a local adapter for the given remote object. This abstract
* method is used by {@link #next()} to convert the remote references
* returned by the remote iterator to local adapters.
* <p>
* Subclasses should implement this method to use the local adapter
* factory to create local adapters of the specific element type.
*
* @param remote remote object
* @return local adapter
*/
protected abstract Object getObject(Object remote);
/**
* Returns the next element in this iteration.
*
* @return next element
* @throws NoSuchElementException if there are no more elements
* @throws RemoteRuntimeException on RMI errors
*/
public Object next() throws NoSuchElementException, RemoteRuntimeException {
try {
advance();
if (buffer == null) {
throw new NoSuchElementException("End of iterator reached");
} else {
return getObject(buffer[positionInBuffer++]);
}
} catch (RemoteException e) {
throw new RemoteRuntimeException(e);
}
}
/**
* Not supported.
*
* @throws UnsupportedOperationException always thrown
*/
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}