001package com.identityworksllc.iiq.common.iterators; 002 003import com.identityworksllc.iiq.common.Functions; 004import org.apache.commons.logging.Log; 005import org.apache.commons.logging.LogFactory; 006import sailpoint.api.SailPointContext; 007import sailpoint.api.SailPointFactory; 008import sailpoint.tools.CloseableIterator; 009import sailpoint.tools.GeneralException; 010import sailpoint.tools.Util; 011 012import java.util.Iterator; 013import java.util.Objects; 014import java.util.concurrent.Callable; 015import java.util.function.Function; 016 017/** 018 * A class that applies a transformation function to each item of an Iterator 019 * before returning it from {@link Iterator#next()}. Basically the Iterator 020 * equivalent of {@link java.util.stream.Stream#map(Function)}. 021 * 022 * If you suppress nulls, all null transformed values will be skipped. 023 * 024 * This is a read-only iterator and does not support remove(). 025 * 026 * @param <In> The input class type 027 * @param <Out> The output class type 028 */ 029public class TransformingIterator<In, Out> implements AutoCloseable, CloseableIterator<Out>, Iterator<Out> { 030 /** 031 * A functional interface similar to {@link java.util.function.Function}, except throwing an exception 032 * @param <In> The input class type 033 * @param <Out> The output class type 034 */ 035 @FunctionalInterface 036 public interface TransformerFunction<In, Out> { 037 /** 038 * Applies the transformation to the given input object, returning an object of the output type 039 * @param input The input object to transform 040 * @return The output object 041 * @throws GeneralException if any failures occur 042 */ 043 Out apply(In input) throws GeneralException; 044 } 045 /** 046 * True after the finalizer is invoked, either on close() or the last hasNext() 047 */ 048 private boolean finalized; 049 /** 050 * Something to invoke after the last item is read 051 */ 052 protected Runnable finalizer; 053 /** 054 * If true, nulls output from the transformation will be dropped. This requires 055 * lookahead behavior which will slightly alter the way this iterator works. 056 */ 057 private boolean ignoreNulls; 058 /** 059 * The input iterator 060 */ 061 private final Iterator<? extends In> input; 062 /** 063 * Tracks the last element if ignoreNulls is enabled 064 */ 065 private Out lastElement; 066 /** 067 * Logger 068 */ 069 private final Log log; 070 /** 071 * True after the first call to next() or hasNext() 072 */ 073 private boolean started; 074 /** 075 * The output transformation function 076 */ 077 protected TransformerFunction<In, Out> transformation; 078 079 /** 080 * Constructor 081 * @param input The iterator being wrapped by this transformer 082 */ 083 public TransformingIterator(Iterator<? extends In> input) { 084 this.input = input; 085 this.log = LogFactory.getLog(this.getClass()); 086 } 087 088 /** 089 * Constructor 090 * @param input The iterator being wrapped by this transformer 091 * @param transformation The transformation to apply to each element of the input iterator 092 */ 093 public TransformingIterator(Iterator<? extends In> input, TransformerFunction<In, Out> transformation) { 094 this.input = input; 095 this.transformation = transformation; 096 this.log = LogFactory.getLog(this.getClass()); 097 } 098 099 /** 100 * @see CloseableIterator#close() 101 */ 102 @Override 103 public void close() { 104 /* Do nothing */ 105 if (input != null) { 106 Util.flushIterator(input); 107 } 108 runFinalizer(); 109 } 110 111 /** 112 * @see Iterator#hasNext() 113 */ 114 @Override 115 public boolean hasNext() { 116 boolean result; 117 Objects.requireNonNull(transformation); 118 started = true; 119 if (ignoreNulls) { 120 if (input.hasNext()) { 121 In nextItem = input.next(); 122 try { 123 Out transformed = transformation.apply(nextItem); 124 while (transformed == null && input.hasNext()) { 125 nextItem = input.next(); 126 transformed = transformation.apply(nextItem); 127 } 128 if (transformed == null) { 129 result = false; 130 } else { 131 lastElement = transformed; 132 result = true; 133 } 134 } catch (GeneralException e) { 135 log.error("Caught an error doing the transform in TransformingIterator", e); 136 throw new IllegalStateException(e); 137 } 138 } else { 139 result = false; 140 } 141 } else { 142 result = input.hasNext(); 143 } 144 145 if (!result) { 146 runFinalizer(); 147 } 148 149 return result; 150 } 151 152 /** 153 * Sets the ignore nulls flag to true 154 */ 155 public TransformingIterator<In, Out> ignoreNulls() { 156 if (started) { 157 throw new IllegalStateException("ignoreNulls() changes iterator behavior and must be called before any call to next() or hasNext()"); 158 } 159 this.ignoreNulls = true; 160 return this; 161 } 162 163 /** 164 * @see Iterator#next() 165 */ 166 @Override 167 public Out next() { 168 Objects.requireNonNull(transformation); 169 170 started = true; 171 if (Thread.interrupted()) { 172 throw new IllegalStateException(new InterruptedException("Thread interrupted")); 173 } 174 if (ignoreNulls) { 175 return lastElement; 176 } else { 177 In value = input.next(); 178 try { 179 return transformation.apply(value); 180 } catch (GeneralException e) { 181 log.error("Caught an error doing the transform in TransformingIterator", e); 182 throw new IllegalStateException(e); 183 } 184 } 185 } 186 187 /** 188 * Runs the finalizer if it has not already been run 189 */ 190 private synchronized void runFinalizer() { 191 if (!finalized && this.finalizer != null) { 192 this.finalizer.run(); 193 this.finalized = true; 194 } 195 } 196 197 public TransformingIterator<In, Out> setFinalizer(Runnable finalizer) { 198 this.finalizer = finalizer; 199 return this; 200 } 201}