summaryrefslogtreecommitdiff
path: root/base/src/main/java/bjc/utils/ioutils/blocks
diff options
context:
space:
mode:
Diffstat (limited to 'base/src/main/java/bjc/utils/ioutils/blocks')
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/Block.java88
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/BlockReader.java73
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/BlockReaders.java81
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/BoundBlockReader.java61
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/FilteredBlockReader.java97
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/FlatMappedBlockReader.java86
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/LayeredBlockReader.java81
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/MappedBlockReader.java54
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/PushbackBlockReader.java106
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/SerialBlockReader.java102
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/SimpleBlockReader.java115
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/ToggledBlockReader.java63
-rw-r--r--base/src/main/java/bjc/utils/ioutils/blocks/TriggeredBlockReader.java70
13 files changed, 1077 insertions, 0 deletions
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/Block.java b/base/src/main/java/bjc/utils/ioutils/blocks/Block.java
new file mode 100644
index 0000000..15f3510
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/Block.java
@@ -0,0 +1,88 @@
+package bjc.utils.ioutils.blocks;
+
+/**
+ * Represents a block of text read in from a source.
+ *
+ * @author EVE
+ *
+ */
+public class Block {
+ /**
+ * The contents of this block.
+ */
+ public final String contents;
+
+ /**
+ * The line of the source this block started on.
+ */
+ public final int startLine;
+
+ /**
+ * The line of the source this block ended on.
+ */
+ public final int endLine;
+
+ /**
+ * The number of this block.
+ */
+ public final int blockNo;
+
+ /**
+ * Create a new block.
+ *
+ * @param blockNo
+ * The number of this block.
+ * @param contents
+ * The contents of this block.
+ * @param startLine
+ * The line this block started on.
+ * @param endLine
+ * The line this block ended.
+ */
+ public Block(final int blockNo, final String contents, final int startLine, final int endLine) {
+ this.contents = contents;
+ this.startLine = startLine;
+ this.endLine = endLine;
+ this.blockNo = blockNo;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+
+ result = prime * result + blockNo;
+ result = prime * result + (contents == null ? 0 : contents.hashCode());
+ result = prime * result + endLine;
+ result = prime * result + startLine;
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (!(obj instanceof Block)) return false;
+
+ final Block other = (Block) obj;
+
+ if (blockNo != other.blockNo) return false;
+
+ if (contents == null) {
+ if (other.contents != null) return false;
+ } else if (!contents.equals(other.contents)) return false;
+
+ if (endLine != other.endLine) return false;
+ if (startLine != other.startLine) return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ String fmt = "Block #%d (from lines %d to %d), length: %d characters";
+
+ return String.format(fmt, blockNo, startLine, endLine, contents.length());
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/BlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/BlockReader.java
new file mode 100644
index 0000000..3c695c6
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/BlockReader.java
@@ -0,0 +1,73 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.function.Consumer;
+
+/**
+ * A source of blocks of characters, marked with line numbers as to block
+ * start/block end.
+ *
+ * @author bjculkin
+ *
+ */
+public interface BlockReader extends AutoCloseable, Iterator<Block> {
+ /**
+ * Check if this reader has an available block.
+ *
+ * @return Whether or not another block is available.
+ */
+ boolean hasNextBlock();
+
+ /**
+ * Get the current block.
+ *
+ * @return The current block, or null if there is no current block.
+ */
+ Block getBlock();
+
+ /**
+ * Move to the next block.
+ *
+ * @return Whether or not the next block was successfully read.
+ */
+ boolean nextBlock();
+
+ /**
+ * Retrieve the number of blocks that have been read so far.
+ *
+ * @return The number of blocks read so far.
+ */
+ int getBlockCount();
+
+ @Override
+ void close() throws IOException;
+
+ /*
+ * Methods with default impls.
+ */
+
+ /**
+ * Execute an action for each remaining block.
+ *
+ * @param action
+ * The action to execute for each block
+ */
+ default void forEachBlock(final Consumer<Block> action) {
+ while (hasNext()) {
+ action.accept(next());
+ }
+ }
+
+ @Override
+ default boolean hasNext() {
+ return hasNextBlock();
+ }
+
+ @Override
+ default Block next() {
+ nextBlock();
+
+ return getBlock();
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/BlockReaders.java b/base/src/main/java/bjc/utils/ioutils/blocks/BlockReaders.java
new file mode 100644
index 0000000..8bbb89c
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/BlockReaders.java
@@ -0,0 +1,81 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.Reader;
+
+/**
+ * Utility methods for constructing instances of {@link BlockReader}
+ *
+ * @author bjculkin
+ *
+ */
+public class BlockReaders {
+ /**
+ * Create a new simple block reader that works off a regex.
+ *
+ * @param blockDelim
+ * The regex that separates blocks.
+ *
+ * @param source
+ * The reader to get blocks from.
+ *
+ * @return A configured simple reader.
+ */
+ public static SimpleBlockReader simple(final String blockDelim, final Reader source) {
+ return new SimpleBlockReader(blockDelim, source);
+ }
+
+ /**
+ * Create a new pushback block reader.
+ *
+ * @param src
+ * The block reader to read blocks from.
+ *
+ * @return A configured pushback reader.
+ */
+ public static PushbackBlockReader pushback(final BlockReader src) {
+ return new PushbackBlockReader(src);
+ }
+
+ /**
+ * Create a new triggered block reader.
+ *
+ * @param source
+ * The block reader to read blocks from.
+ *
+ * @param action
+ * The action to execute before reading a block.
+ *
+ * @return A configured triggered block reader.
+ */
+ public static BlockReader trigger(final BlockReader source, final Runnable action) {
+ return new TriggeredBlockReader(source, action);
+ }
+
+ /**
+ * Create a new layered block reader.
+ *
+ * @param primary
+ * The first source to read blocks from.
+ *
+ * @param secondary
+ * The second source to read blocks from.
+ *
+ * @return A configured layered block reader.
+ */
+ public static BlockReader layered(final BlockReader primary, final BlockReader secondary) {
+ return new LayeredBlockReader(primary, secondary);
+ }
+
+ /**
+ * Create a new serial block reader.
+ *
+ * @param readers
+ * The readers to pull from, in the order to pull from
+ * them.
+ *
+ * @return A configured serial block reader.
+ */
+ public static BlockReader serial(final BlockReader... readers) {
+ return new SerialBlockReader(readers);
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/BoundBlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/BoundBlockReader.java
new file mode 100644
index 0000000..b1e82d7
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/BoundBlockReader.java
@@ -0,0 +1,61 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+
+import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
+
+public class BoundBlockReader implements BlockReader {
+ @FunctionalInterface
+ public interface Closer {
+ public void close() throws IOException;
+ }
+
+ private BooleanSupplier checker;
+ private Supplier<Block> getter;
+ private Closer closer;
+
+ private Block current;
+
+ private int blockNo;
+
+ public BoundBlockReader(BooleanSupplier blockChecker, Supplier<Block> blockGetter, Closer blockCloser) {
+ checker = blockChecker;
+ getter = blockGetter;
+ closer = blockCloser;
+
+ blockNo = 0;
+ }
+
+ @Override
+ public boolean hasNextBlock() {
+ return checker.getAsBoolean();
+ }
+
+ @Override
+ public Block getBlock() {
+ return current;
+ }
+
+ @Override
+ public boolean nextBlock() {
+ if(checker.getAsBoolean()) {
+ current = getter.get();
+ blockNo += 1;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws IOException {
+ closer.close();
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/FilteredBlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/FilteredBlockReader.java
new file mode 100644
index 0000000..0b43f7a
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/FilteredBlockReader.java
@@ -0,0 +1,97 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+public class FilteredBlockReader implements BlockReader {
+ /*
+ * The source of blocks.
+ */
+ private BlockReader source;
+
+ /*
+ * The current and next block.
+ *
+ * Both have already been checked for the predicate.
+ */
+ private Block current;
+ private Block pending;
+
+ /*
+ * Number of blocks that passed the predicate.
+ */
+ private int blockNo;
+
+ /*
+ * The predicate blocks must pass.
+ */
+ private Predicate<Block> pred;
+
+ /*
+ * The action to call on failure, if there is one.
+ */
+ private Consumer<Block> failAction;
+
+ public FilteredBlockReader(BlockReader src, Predicate<Block> predic) {
+ this(src, predic, null);
+ }
+
+ public FilteredBlockReader(BlockReader src, Predicate<Block> predic, Consumer<Block> failAct) {
+ source = src;
+ pred = predic;
+ failAction = failAct;
+
+ blockNo = 0;
+ }
+
+ @Override
+ public boolean hasNextBlock() {
+ if(pending != null) return true;
+
+ while(source.hasNextBlock()) {
+ /*
+ * Only say we have a next block if the next block would
+ * pass the predicate.
+ */
+ pending = source.next();
+
+ if(pred.test(pending)) {
+ blockNo += 1;
+ return true;
+ } else {
+ failAction.accept(pending);
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public Block getBlock() {
+ return current;
+ }
+
+ @Override
+ public boolean nextBlock() {
+ if(pending != null || hasNextBlock()) {
+ current = pending;
+ pending = null;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws IOException {
+ source.close();
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/FlatMappedBlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/FlatMappedBlockReader.java
new file mode 100644
index 0000000..f4d8439
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/FlatMappedBlockReader.java
@@ -0,0 +1,86 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+
+/**
+ * A block reader that supports applying a flatmap operation to blocks.
+ *
+ * The use-case in mind for this was tokenizing blocks.
+ *
+ * @author Benjamin Culkin
+ */
+public class FlatMappedBlockReader implements BlockReader {
+ /*
+ * The source reader.
+ */
+ private BlockReader reader;
+
+ /*
+ * The current block, and any blocks pending from the last source block.
+ */
+ private Iterator<Block> pending;
+ private Block current;
+
+ /*
+ * The operator to open blocks with.
+ */
+ private Function<Block, List<Block>> transform;
+
+ /*
+ * The current block number.
+ */
+ private int blockNo;
+
+ public FlatMappedBlockReader(BlockReader source, Function<Block, List<Block>> trans) {
+ reader = source;
+ transform = trans;
+
+ blockNo = 0;
+ }
+
+ @Override
+ public boolean hasNextBlock() {
+ return pending.hasNext() || reader.hasNextBlock();
+ }
+
+ @Override
+ public Block getBlock() {
+ return current;
+ }
+
+ @Override
+ public boolean nextBlock() {
+ /*
+ * Attempt to get a new pending list if the one we have isn't
+ * valid.
+ */
+ while(pending == null || !pending.hasNext()) {
+ if(!reader.hasNext()) return false;
+
+ pending = transform.apply(reader.next()).iterator();
+ }
+
+ /*
+ * Advance the iterator.
+ */
+ current = pending.next();
+ blockNo += 1;
+
+ return true;
+ }
+
+ @Override
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws IOException {
+ reader.close();
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/LayeredBlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/LayeredBlockReader.java
new file mode 100644
index 0000000..967a1f2
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/LayeredBlockReader.java
@@ -0,0 +1,81 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+
+/**
+ * A block reader that supports draining all the blocks from one reading before
+ * swapping to another.
+ *
+ * This is more a 'prioritize blocks from one over the other', than a 'read all
+ * the blocks from one, then all the blocks from the other'. If you need that,
+ * look at {@link SerialBlockReader}.
+ *
+ * @author bjculkin
+ *
+ */
+public class LayeredBlockReader implements BlockReader {
+ /*
+ * The readers to drain from.
+ */
+ private final BlockReader first;
+ private final BlockReader second;
+
+ /*
+ * The current block number.
+ */
+ private int blockNo;
+
+ /**
+ * Create a new layered block reader.
+ *
+ * @param primary
+ * The first source to read blocks from.
+ *
+ * @param secondary
+ * The second source to read blocks from.
+ */
+ public LayeredBlockReader(final BlockReader primary, final BlockReader secondary) {
+ first = primary;
+ second = secondary;
+ }
+
+ @Override
+ public boolean hasNextBlock() {
+ return first.hasNextBlock() || second.hasNextBlock();
+ }
+
+ @Override
+ public Block getBlock() {
+ final Block firstBlock = first.getBlock();
+
+ /*
+ * Only drain a block from the second reader if none are
+ * available in the first reader.
+ */
+ return firstBlock == null ? second.getBlock() : firstBlock;
+ }
+
+ @Override
+ public boolean nextBlock() {
+ final boolean gotFirst = first.nextBlock();
+ final boolean succ = gotFirst ? gotFirst : second.nextBlock();
+
+ if (succ) {
+ blockNo += 1;
+ }
+
+ return succ;
+ }
+
+ @Override
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws IOException {
+ second.close();
+
+ first.close();
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/MappedBlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/MappedBlockReader.java
new file mode 100644
index 0000000..12fa848
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/MappedBlockReader.java
@@ -0,0 +1,54 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+
+import java.util.function.UnaryOperator;
+
+public class MappedBlockReader implements BlockReader {
+ private BlockReader reader;
+
+ private Block current;
+
+ private UnaryOperator<Block> transform;
+
+ private int blockNo;
+
+ public MappedBlockReader(BlockReader source, UnaryOperator<Block> trans) {
+ reader = source;
+ transform = trans;
+
+ blockNo = 0;
+ }
+
+ @Override
+ public boolean hasNextBlock() {
+ return reader.hasNextBlock();
+ }
+
+ @Override
+ public Block getBlock() {
+ return current;
+ }
+
+ @Override
+ public boolean nextBlock() {
+ if(hasNextBlock()) {
+ current = transform.apply(reader.next());
+ blockNo += 1;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws IOException {
+ reader.close();
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/PushbackBlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/PushbackBlockReader.java
new file mode 100644
index 0000000..0cc9dea
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/PushbackBlockReader.java
@@ -0,0 +1,106 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+import java.util.Deque;
+import java.util.LinkedList;
+
+/**
+ * A block reader that supports pushing blocks onto the input queue so that they
+ * are provided before blocks read from an input source.
+ *
+ * @author bjculkin
+ *
+ */
+public class PushbackBlockReader implements BlockReader {
+ private final BlockReader source;
+
+ /*
+ * The queue of pushed-back blocks.
+ */
+ private final Deque<Block> waiting;
+
+ private Block curBlock;
+
+ private int blockNo;
+
+ /**
+ * Create a new pushback block reader.
+ *
+ * @param src
+ * The block reader to use when no blocks are queued.
+ */
+ public PushbackBlockReader(final BlockReader src) {
+ source = src;
+
+ waiting = new LinkedList<>();
+ }
+
+ @Override
+ public boolean hasNextBlock() {
+ return !waiting.isEmpty() || source.hasNextBlock();
+ }
+
+ @Override
+ public Block getBlock() {
+ return curBlock;
+ }
+
+ @Override
+ public boolean nextBlock() {
+ /*
+ * Drain pushed-back blocks first.
+ */
+ if (!waiting.isEmpty()) {
+ curBlock = waiting.pop();
+
+ blockNo += 1;
+
+ return true;
+ } else {
+ final boolean succ = source.nextBlock();
+ curBlock = source.getBlock();
+
+ if (succ) {
+ blockNo += 1;
+ }
+
+ return succ;
+ }
+ }
+
+ @Override
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws IOException {
+ source.close();
+ }
+
+ /**
+ * Insert a block at the back of the queue of pending blocks.
+ *
+ * @param blk
+ * The block to put at the back.
+ */
+ public void addBlock(final Block blk) {
+ waiting.add(blk);
+ }
+
+ /**
+ * Insert a block at the front of the queue of pending blocks.
+ *
+ * @param blk
+ * The block to put at the front.
+ */
+ public void pushBlock(final Block blk) {
+ waiting.push(blk);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PushbackBlockReader [waiting=%s, curBlock=%s, blockNo=%s]", waiting, curBlock,
+ blockNo);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/SerialBlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/SerialBlockReader.java
new file mode 100644
index 0000000..c229da1
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/SerialBlockReader.java
@@ -0,0 +1,102 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+import java.util.Deque;
+
+/**
+ * Provides a means of concatenating two block readers.
+ *
+ * @author bjculkin
+ *
+ */
+public class SerialBlockReader implements BlockReader {
+ private Deque<BlockReader> readerQueue;
+
+ private int blockNo;
+
+ /**
+ * Create a new serial block reader.
+ *
+ * @param readers
+ * The readers to pull from, in the order to pull from
+ * them.
+ */
+ public SerialBlockReader(final BlockReader... readers) {
+ for (final BlockReader reader : readers) {
+ readerQueue.add(reader);
+ }
+ }
+
+ @Override
+ public boolean hasNextBlock() {
+ if (readerQueue.isEmpty()) return false;
+
+ /*
+ * Attempt to get a block from the first reader.
+ */
+ boolean hasBlock = readerQueue.peek().hasNextBlock();
+ boolean cont = hasBlock || readerQueue.isEmpty();
+
+ /*
+ * Close/dispose of readers until we get an open one.
+ */
+ while (!cont) {
+ try {
+ readerQueue.pop().close();
+ } catch (final IOException ioex) {
+ throw new IllegalStateException("Exception thrown by discarded reader", ioex);
+ }
+
+ hasBlock = readerQueue.peek().hasNextBlock();
+ cont = hasBlock || readerQueue.isEmpty();
+ }
+
+ return hasBlock;
+ }
+
+ @Override
+ public Block getBlock() {
+ if (readerQueue.isEmpty())
+ return null;
+ else return readerQueue.peek().getBlock();
+ }
+
+ @Override
+ public boolean nextBlock() {
+ if (readerQueue.isEmpty()) return false;
+
+ boolean gotBlock = readerQueue.peek().nextBlock();
+ boolean cont = gotBlock || readerQueue.isEmpty();
+
+ while (!cont) {
+ try {
+ readerQueue.pop().close();
+ } catch (final IOException ioex) {
+ throw new IllegalStateException("Exception thrown by discarded reader", ioex);
+ }
+
+ gotBlock = readerQueue.peek().nextBlock();
+ cont = gotBlock || readerQueue.isEmpty();
+ }
+
+ if (cont) {
+ blockNo += 1;
+ }
+
+ return cont;
+ }
+
+ @Override
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws IOException {
+ while (!readerQueue.isEmpty()) {
+ final BlockReader reader = readerQueue.pop();
+
+ reader.close();
+ }
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/SimpleBlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/SimpleBlockReader.java
new file mode 100644
index 0000000..734bde8
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/SimpleBlockReader.java
@@ -0,0 +1,115 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+import java.util.regex.Pattern;
+
+import bjc.utils.funcutils.StringUtils;
+/**
+ * Simple implementation of {@link BlockReader}
+ *
+ * NOTE: The EOF marker is always treated as a delimiter. You are expected to
+ * handle blocks that may be shorter than you expect.
+ *
+ * @author EVE
+ *
+ */
+public class SimpleBlockReader implements BlockReader {
+ /*
+ * I/O source for blocks.
+ */
+ private final Scanner blockReader;
+
+ /*
+ * The current block.
+ */
+ private Block currBlock;
+
+ /*
+ * Info about the current block.
+ */
+ private int blockNo;
+ private int lineNo;
+
+ /**
+ * Create a new block reader.
+ *
+ * @param blockDelim
+ * The pattern that separates blocks. Note that the end
+ * of file is always considered to end a block.
+ *
+ * @param source
+ * The source to read blocks from.
+ */
+ public SimpleBlockReader(final String blockDelim, final Reader source) {
+ blockReader = new Scanner(source);
+
+ final String pattern = String.format("(?:%s)|\\Z", blockDelim);
+ final Pattern pt = Pattern.compile(pattern, Pattern.MULTILINE);
+
+ blockReader.useDelimiter(pt);
+
+ lineNo = 1;
+ }
+
+ @Override
+ public boolean hasNextBlock() {
+ return blockReader.hasNext();
+ }
+
+ @Override
+ public Block getBlock() {
+ return currBlock;
+ }
+
+ @Override
+ public boolean nextBlock() {
+ try {
+ /*
+ * Read in a new block, and keep the line numbers sane.
+ */
+ final int blockStartLine = lineNo;
+ final String blockContents = blockReader.next();
+ final int blockEndLine = lineNo + StringUtils.countMatches(blockContents, "\\R");
+
+ lineNo = blockEndLine;
+ blockNo += 1;
+
+ currBlock = new Block(blockNo, blockContents, blockStartLine, blockEndLine);
+
+ return true;
+ } catch (final NoSuchElementException nseex) {
+ currBlock = null;
+
+ return false;
+ }
+ }
+
+ @Override
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws IOException {
+ blockReader.close();
+ }
+
+ /**
+ * Set the delimiter used to separate blocks.
+ *
+ * @param delim
+ * The delimiter used to separate blocks.
+ */
+ public void setDelimiter(final String delim) {
+ blockReader.useDelimiter(delim);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("SimpleBlockReader [currBlock=%s, blockNo=%s]", currBlock, blockNo);
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/ToggledBlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/ToggledBlockReader.java
new file mode 100644
index 0000000..8f39b8f
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/ToggledBlockReader.java
@@ -0,0 +1,63 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+
+import bjc.utils.data.BooleanToggle;
+
+public class ToggledBlockReader implements BlockReader {
+ private BlockReader leftSource;
+ private BlockReader rightSource;
+
+ /*
+ * We choose the left source when this is true.
+ */
+ private BooleanToggle leftToggle;
+
+ private int blockNo;
+
+ public ToggledBlockReader(BlockReader left, BlockReader right) {
+ leftSource = left;
+ rightSource = right;
+
+ blockNo = 0;
+
+ leftToggle = new BooleanToggle();
+ }
+
+ @Override
+ public boolean hasNextBlock() {
+ if(leftToggle.peek()) return leftSource.hasNextBlock();
+ else return rightSource.hasNextBlock();
+ }
+
+ @Override
+ public Block getBlock() {
+ if(leftToggle.peek()) return leftSource.getBlock();
+ else return rightSource.getBlock();
+ }
+
+ @Override
+ public boolean nextBlock() {
+ boolean succ;
+
+ if(leftToggle.get()) {
+ succ = leftSource.nextBlock();
+ } else {
+ succ = rightSource.nextBlock();
+ }
+
+ if(succ) blockNo += 1;
+ return succ;
+ }
+
+ @Override
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws IOException {
+ leftSource.close();
+ rightSource.close();
+ }
+}
diff --git a/base/src/main/java/bjc/utils/ioutils/blocks/TriggeredBlockReader.java b/base/src/main/java/bjc/utils/ioutils/blocks/TriggeredBlockReader.java
new file mode 100644
index 0000000..3a1e393
--- /dev/null
+++ b/base/src/main/java/bjc/utils/ioutils/blocks/TriggeredBlockReader.java
@@ -0,0 +1,70 @@
+package bjc.utils.ioutils.blocks;
+
+import java.io.IOException;
+
+/**
+ * A block reader that fires an action before a block is actually read.
+ *
+ * @author bjculkin
+ *
+ */
+public class TriggeredBlockReader implements BlockReader {
+ private final BlockReader source;
+
+ private int blockNo;
+
+ /*
+ * The action to fire.
+ */
+ private final Runnable action;
+
+ /**
+ * Create a new triggered reader with the specified source/action.
+ *
+ * @param source
+ * The block reader to read blocks from.
+ *
+ * @param action
+ * The action to execute before reading a block.
+ */
+ public TriggeredBlockReader(final BlockReader source, final Runnable action) {
+ this.source = source;
+ this.action = action;
+
+ blockNo = 0;
+ }
+
+ @Override
+ public boolean hasNextBlock() {
+ action.run();
+
+ return source.hasNextBlock();
+ }
+
+ @Override
+ public Block getBlock() {
+ return source.getBlock();
+ }
+
+ @Override
+ public boolean nextBlock() {
+ blockNo += 1;
+
+ return source.nextBlock();
+ }
+
+ @Override
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws IOException {
+ source.close();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TriggeredBlockReader [source=%s]", source);
+ }
+}