summaryrefslogtreecommitdiff
path: root/BJC-Utils2/src/main/java/bjc/utils/ioutils
diff options
context:
space:
mode:
authorbculkin2442 <bjculkin@mix.wvu.edu>2017-03-25 20:34:42 -0400
committerbculkin2442 <bjculkin@mix.wvu.edu>2017-03-25 20:34:42 -0400
commitb53c1ef47c438fb1144b961d1939c37a4bfe9120 (patch)
treeeec3e8737831dfa245fdee31fb4b25bffac03aff /BJC-Utils2/src/main/java/bjc/utils/ioutils
parent9716b1ac09eb92c4ed001be4350d54b41b953239 (diff)
Separate general I/O from parsing.
Diffstat (limited to 'BJC-Utils2/src/main/java/bjc/utils/ioutils')
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/ioutils/BlockReader.java208
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/ioutils/RuleBasedConfigReader.java215
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/ioutils/RuleBasedReaderPragmas.java80
3 files changed, 503 insertions, 0 deletions
diff --git a/BJC-Utils2/src/main/java/bjc/utils/ioutils/BlockReader.java b/BJC-Utils2/src/main/java/bjc/utils/ioutils/BlockReader.java
new file mode 100644
index 0000000..33bbbd5
--- /dev/null
+++ b/BJC-Utils2/src/main/java/bjc/utils/ioutils/BlockReader.java
@@ -0,0 +1,208 @@
+package bjc.utils.ioutils;
+
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+import java.util.function.Consumer;
+import java.util.regex.Pattern;
+
+/**
+ * Implements reading numbered blocks from a source.
+ *
+ * A block is a series of characters, seperated by some regular expression.
+ *
+ * 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 BlockReader implements AutoCloseable {
+ /**
+ * Represents a block of text read in from a source.
+ *
+ * @author EVE
+ *
+ */
+ public static 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(int blockNo, String contents, int startLine, 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(Object obj) {
+ if(this == obj) return true;
+ if(obj == null) return false;
+ if(!(obj instanceof Block)) return false;
+
+ 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() {
+ return String.format("Block #%d (from lines %d to %d) length: %d characters", blockNo, startLine,
+ endLine, contents.length());
+ }
+ }
+
+ /*
+ * I/O source for blocks.
+ */
+ private LineNumberReader lnReader;
+ private Scanner blockReader;
+
+ /*
+ * The current block.
+ */
+ private Block currBlock;
+ private int blockNo;
+
+ /**
+ * 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 BlockReader(String blockDelim, Reader source) {
+ lnReader = new LineNumberReader(source);
+
+ blockReader = new Scanner(lnReader);
+
+ String pattern = String.format("(?:%s)|\\Z", blockDelim);
+ Pattern pt = Pattern.compile(pattern, Pattern.MULTILINE);
+
+ blockReader.useDelimiter(pt);
+ }
+
+ /**
+ * Check if this reader has an available block.
+ *
+ * @return Whether or not another block is available.
+ */
+ public boolean hasNextBlock() {
+ return blockReader.hasNext();
+ }
+
+ /**
+ * Get the current block.
+ *
+ * @return The current block, or null if there is no current block.
+ */
+ public Block getBlock() {
+ return currBlock;
+ }
+
+ /**
+ * Move to the next block.
+ *
+ * @return Whether or not the next block was succesfully read.
+ */
+ public boolean nextBlock() {
+ try {
+ int blockStartLine = lnReader.getLineNumber();
+ String blockContents = blockReader.next();
+ int blockEndLine = lnReader.getLineNumber();
+ blockNo += 1;
+
+ currBlock = new Block(blockNo, blockContents, blockStartLine, blockEndLine);
+
+ return true;
+ } catch(NoSuchElementException nseex) {
+ return false;
+ }
+ }
+
+ /**
+ * Execute an action for each remaining block.
+ *
+ * @param action
+ * The action to execute for each block
+ */
+ public void forEachBlock(Consumer<Block> action) {
+ while(hasNextBlock()) {
+ nextBlock();
+
+ action.accept(currBlock);
+ }
+ }
+
+ /**
+ * Retrieve the number of blocks that have been read so far.
+ *
+ * @return The number of blocks read so far.
+ */
+ public int getBlockCount() {
+ return blockNo;
+ }
+
+ @Override
+ public void close() throws Exception {
+ blockReader.close();
+
+ lnReader.close();
+ }
+}
diff --git a/BJC-Utils2/src/main/java/bjc/utils/ioutils/RuleBasedConfigReader.java b/BJC-Utils2/src/main/java/bjc/utils/ioutils/RuleBasedConfigReader.java
new file mode 100644
index 0000000..b4d56a1
--- /dev/null
+++ b/BJC-Utils2/src/main/java/bjc/utils/ioutils/RuleBasedConfigReader.java
@@ -0,0 +1,215 @@
+package bjc.utils.ioutils;
+
+import bjc.utils.data.IHolder;
+import bjc.utils.data.IPair;
+import bjc.utils.data.Identity;
+import bjc.utils.data.Pair;
+import bjc.utils.exceptions.UnknownPragmaException;
+import bjc.utils.funcdata.FunctionalMap;
+import bjc.utils.funcdata.FunctionalStringTokenizer;
+import bjc.utils.funcdata.IMap;
+
+import java.io.InputStream;
+import java.util.InputMismatchException;
+import java.util.Scanner;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * This class parses a rules based config file, and uses it to drive a provided
+ * set of actions
+ *
+ * @author ben
+ *
+ * @param <E>
+ * The type of the state object to use
+ *
+ */
+public class RuleBasedConfigReader<E> {
+ // Function to execute when starting a rule
+ // Takes the tokenizer, and a pair of the read token and application
+ // state
+ private BiConsumer<FunctionalStringTokenizer, IPair<String, E>> start;
+
+ // Function to use when continuing a rule
+ // Takes a tokenizer and application state
+ private BiConsumer<FunctionalStringTokenizer, E> continueRule;
+
+ // Function to use when ending a rule
+ // Takes an application state
+ private Consumer<E> end;
+
+ // Map of pragma names to pragma actions
+ // Pragma actions are functions taking a tokenizer and application state
+ private IMap<String, BiConsumer<FunctionalStringTokenizer, E>> pragmas;
+
+ /**
+ * Create a new rule-based config reader
+ *
+ * @param start
+ * The action to fire when starting a rule
+ * @param continueRule
+ * The action to fire when continuing a rule
+ * @param end
+ * The action to fire when ending a rule
+ */
+ public RuleBasedConfigReader(BiConsumer<FunctionalStringTokenizer, IPair<String, E>> start,
+ BiConsumer<FunctionalStringTokenizer, E> continueRule, Consumer<E> end) {
+ this.start = start;
+ this.continueRule = continueRule;
+ this.end = end;
+
+ this.pragmas = new FunctionalMap<>();
+ }
+
+ /**
+ * Add a pragma to this reader
+ *
+ * @param name
+ * The name of the pragma to add
+ * @param action
+ * The function to execute when this pragma is read
+ */
+ public void addPragma(String name, BiConsumer<FunctionalStringTokenizer, E> action) {
+ if(name == null)
+ throw new NullPointerException("Pragma name must not be null");
+ else if(action == null) throw new NullPointerException("Pragma action must not be null");
+
+ pragmas.put(name, action);
+ }
+
+ private void continueRule(E state, boolean isRuleOpen, String line) {
+ // Make sure our input is correct
+ if(isRuleOpen == false)
+ throw new InputMismatchException("Cannot continue rule with no rule open");
+ else if(continueRule == null)
+ throw new InputMismatchException("Rule continuation not supported for current grammar");
+
+ // Accept the rule
+ continueRule.accept(new FunctionalStringTokenizer(line.substring(1), " "), state);
+ }
+
+ private boolean endRule(E state, boolean isRuleOpen) {
+ // Ignore blank line without an open rule
+ if(isRuleOpen == false)
+ // Do nothing
+ return false;
+ else {
+ // Nothing happens on rule end
+ if(end != null) {
+ // Process the rule ending
+ end.accept(state);
+ }
+
+ // Return a closed rule
+ return false;
+ }
+ }
+
+ /**
+ * Run a stream through this reader
+ *
+ * @param input
+ * The stream to get input
+ * @param initialState
+ * The initial state of the reader
+ * @return The final state of the reader
+ */
+ public E fromStream(InputStream input, E initialState) {
+ if(input == null) throw new NullPointerException("Input stream must not be null");
+
+ // Application state: We're giving this back later
+ E state = initialState;
+
+ // Prepare our input source
+ try(Scanner source = new Scanner(input)) {
+ source.useDelimiter("\n");
+ // This is true when a rule's open
+ IHolder<Boolean> isRuleOpen = new Identity<>(false);
+
+ // Do something for every line of the file
+ source.forEachRemaining((line) -> {
+ // Skip comment lines
+ if(line.startsWith("#") || line.startsWith("//"))
+ // It's a comment
+ return;
+ else if(line.equals("")) {
+ // End the rule
+ isRuleOpen.replace(endRule(state, isRuleOpen.getValue()));
+ } else if(line.startsWith("\t")) {
+ // Continue the rule
+ continueRule(state, isRuleOpen.getValue(), line);
+ } else {
+ // Open a rule
+ isRuleOpen.replace(startRule(state, isRuleOpen.getValue(), line));
+ }
+ });
+ }
+
+ // Return the state that the user has created
+ return state;
+
+ }
+
+ /**
+ * Set the action to execute when continuing a rule
+ *
+ * @param continueRule
+ * The action to execute on continuation of a rule
+ */
+ public void setContinueRule(BiConsumer<FunctionalStringTokenizer, E> continueRule) {
+ this.continueRule = continueRule;
+ }
+
+ /**
+ * Set the action to execute when ending a rule
+ *
+ * @param end
+ * The action to execute on ending of a rule
+ */
+ public void setEndRule(Consumer<E> end) {
+ this.end = end;
+ }
+
+ /**
+ * Set the action to execute when starting a rule
+ *
+ * @param start
+ * The action to execute on starting of a rule
+ */
+ public void setStartRule(BiConsumer<FunctionalStringTokenizer, IPair<String, E>> start) {
+ if(start == null) throw new NullPointerException("Action on rule start must be non-null");
+
+ this.start = start;
+ }
+
+ private boolean startRule(E state, boolean isRuleOpen, String line) {
+ // Create the line tokenizer
+ FunctionalStringTokenizer tokenizer = new FunctionalStringTokenizer(line, " ");
+
+ // Get the initial token
+ String nextToken = tokenizer.nextToken();
+
+ // Handle pragmas
+ if(nextToken.equals("pragma")) {
+ // Get the pragma name
+ String token = tokenizer.nextToken();
+
+ // Handle pragmas
+ pragmas.getOrDefault(token, (tokenzer, stat) -> {
+ throw new UnknownPragmaException("Unknown pragma " + token);
+ }).accept(tokenizer, state);
+ } else {
+ // Make sure input is correct
+ if(isRuleOpen == true)
+ throw new InputMismatchException("Nested rules are currently not supported");
+
+ // Start a rule
+ start.accept(tokenizer, new Pair<>(nextToken, state));
+
+ isRuleOpen = true;
+ }
+
+ return isRuleOpen;
+ }
+}
diff --git a/BJC-Utils2/src/main/java/bjc/utils/ioutils/RuleBasedReaderPragmas.java b/BJC-Utils2/src/main/java/bjc/utils/ioutils/RuleBasedReaderPragmas.java
new file mode 100644
index 0000000..f19eb2c
--- /dev/null
+++ b/BJC-Utils2/src/main/java/bjc/utils/ioutils/RuleBasedReaderPragmas.java
@@ -0,0 +1,80 @@
+package bjc.utils.ioutils;
+
+import bjc.utils.exceptions.PragmaFormatException;
+import bjc.utils.funcdata.FunctionalStringTokenizer;
+import bjc.utils.funcutils.ListUtils;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Contains factory methods for common pragma types
+ *
+ * @author ben
+ *
+ */
+public class RuleBasedReaderPragmas {
+
+ /**
+ * Creates a pragma that takes a single integer argument
+ *
+ * @param <StateType>
+ * The type of state that goes along with this pragma
+ * @param name
+ * The name of this pragma, for error message purpose
+ * @param consumer
+ * The function to invoke with the parsed integer
+ * @return A pragma that functions as described above.
+ */
+ public static <StateType> BiConsumer<FunctionalStringTokenizer, StateType> buildInteger(String name,
+ BiConsumer<Integer, StateType> consumer) {
+ return (tokenizer, state) -> {
+ // Check our input is correct
+ if(!tokenizer.hasMoreTokens())
+ throw new PragmaFormatException("Pragma " + name + " requires one integer argument");
+
+ // Read the argument
+ String token = tokenizer.nextToken();
+
+ try {
+ // Run the pragma
+ consumer.accept(Integer.parseInt(token), state);
+ } catch(NumberFormatException nfex) {
+ // Tell the user their argument isn't correct
+ PragmaFormatException pfex = new PragmaFormatException(
+ "Argument " + token + " to " + name + " pragma isn't a valid integer. "
+ + "This pragma requires a integer argument");
+
+ pfex.initCause(nfex);
+
+ throw pfex;
+ }
+ };
+ }
+
+ /**
+ * Creates a pragma that takes any number of arguments and collapses
+ * them all into a single string
+ *
+ * @param <StateType>
+ * The type of state that goes along with this pragma
+ * @param name
+ * The name of this pragma, for error message purpose
+ * @param consumer
+ * The function to invoke with the parsed string
+ * @return A pragma that functions as described above.
+ */
+ public static <StateType> BiConsumer<FunctionalStringTokenizer, StateType> buildStringCollapser(String name,
+ BiConsumer<String, StateType> consumer) {
+ return (tokenizer, state) -> {
+ // Check our input
+ if(!tokenizer.hasMoreTokens()) throw new PragmaFormatException(
+ "Pragma " + name + " requires one or more string arguments");
+
+ // Build our argument
+ String collapsed = ListUtils.collapseTokens(tokenizer.toList());
+
+ // Run the pragma
+ consumer.accept(collapsed, state);
+ };
+ }
+}