/*
* 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.stanbol.entityhub.servicesapi.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Uses the parsed Adapter to convert values of type T to values of type
* A. If an instance of T can not be converted to A, than such values are
* filtered. This means that this implementation can be used for both filtering
* and converting of values of the base iterator. In fact the
* FilteringIterator is implemented based on this class.<p>
* Note that {@link Iterator#remove()} only works as long as
* {@link Iterator#hasNext()} was not called to determine if there are
* further elements. The reason for that is, that in order to filter elements
* of the parent iterator {@link Iterator#next()} has to be called to check
* weather any further element is valid against the used Filter.
* This call to {@link Iterator#next()} causes the parent Iterator to switch
* to the next element, meaning that after that the <code>remove()</code>
* method would delete a different element. To avoid that this Iterator
* throws an {@link IllegalStateException} in such cases. If the parent
* Iterator does not support <code>remove()</code> at all an
* {@link UnsupportedOperationException} is thrown. <p>
*
* @author Rupert Westenthaler
*
* @param <T> The type of the incoming elements
* @param <A> The type of the elements returned by this iterator
*/
public class AdaptingIterator<T,A> implements Iterator<A> {
/**
* Adapts values of type T to values of type A. <code>null</code> indicated
* that the adaption is not possible for the current value of T
*
* @author Rupert Westenthaler
*
* @param <T>
* @param <A>
*/
public interface Adapter<T,A> {
/**
* Converts the value of type T to a value of type A. If an instance of
* T can not be converted to A, than <code>null</code> is returned
* @param value the incoming value
* @param type the target type
* @return the converted value or <code>null</code> if the parsed value
* is <code>null</code> or the parsed value can not be converted
*/
A adapt(T value, Class<A> type);
}
private final Adapter<T, A> adapter;
private final Iterator<T> it;
private final Class<A> type;
private A next;
private Boolean hasNext;
/**
* Constructs an instance based on an iterator of type T, an adapter and the
* target type
* @param it the base iterator
* @param adapter the adapter
* @param type the target type
*/
public AdaptingIterator(Iterator<T> it,Adapter<T,A> adapter,Class<A> type){
if(it == null){
throw new IllegalArgumentException("Parsed iterator MUST NOT be NULL!");
}
if(adapter == null){
throw new IllegalArgumentException("Parsed adapter MUST NOT be NULL!");
}
if(type == null){
throw new IllegalArgumentException("Parsed type MUST NOT be NULL!");
}
this.it = it;
this.adapter = adapter;
this.type = type;
}
@Override
public final boolean hasNext() {
if(hasNext == null){ // only once even with multiple calls
next = prepareNext();
hasNext = next != null;
}
return hasNext;
}
@Override
public final A next() {
hasNext(); //call hasNext (to init next Element if not already done)
if(!hasNext){
throw new NoSuchElementException();
} else {
A current = next;
next = null;
hasNext = null;
return current;
}
}
/**
* This implementation of remove does have an additional restriction. It
* is only able to remove the current element of the parent Iterator (parsed
* in the constructor) if {@link #hasNext()} was not yet called. This is
* because {@link #hasNext()} needs to call {@link Iterator#next()} on the
* parent iterator to check if there are further elements that can be
* adapted successfully. This causes that the current element of this
* Iterator (stored in an local variable) is no longer the current element
* of the parent iterator and therefore calls to {@link #remove()} would
* delete an other object within the collection holding the elements used
* for this iteration. To prevent this this method throws an
* {@link IllegalStateException} ins such cases. Users of this method need
* therefore to ensure, that there are no calls to remove between a call
* to {@link #hasNext()} and {@link #next()} (what is not the case in
* typical use cases).
* @see Iterator#remove()
*/
@Override
public final void remove() {
/*
* TODO: See java doc for a detailed description!
* If someone has a better Idea how to solve this please let me know!
* all the best
* Rupert Westenthaler
*/
if(hasNext!= null){
throw new IllegalStateException("Remove can not be called after calling hasNext()! See java doc for more information.");
}
it.remove();
}
protected A prepareNext(){
T check;
A converted;
while(it.hasNext()){
check = it.next();
converted = adapter.adapt(check,type);
if(converted != null){
return converted;
}
}
return null;
}
}