summaryrefslogtreecommitdiff
path: root/base/src/main/java/bjc/utils/components
diff options
context:
space:
mode:
authorBenjamin J. Culkin <bjculkin@mix.wvu.edu>2017-10-08 22:39:59 -0300
committerBenjamin J. Culkin <bjculkin@mix.wvu.edu>2017-10-08 22:39:59 -0300
commitc82e3b3b2de0633317ec8fc85925e91422820597 (patch)
tree96567416ce23c5ce85601f9cedc3a94bb1c55cba /base/src/main/java/bjc/utils/components
parentb3ac1c8690c3e14c879913e5dcc03a5f5e14876e (diff)
Start splitting into maven modules
Diffstat (limited to 'base/src/main/java/bjc/utils/components')
-rw-r--r--base/src/main/java/bjc/utils/components/ComponentDescription.java135
-rw-r--r--base/src/main/java/bjc/utils/components/ComponentDescriptionFileParser.java65
-rw-r--r--base/src/main/java/bjc/utils/components/ComponentDescriptionState.java144
-rw-r--r--base/src/main/java/bjc/utils/components/FileComponentRepository.java181
-rw-r--r--base/src/main/java/bjc/utils/components/IComponentRepository.java49
-rw-r--r--base/src/main/java/bjc/utils/components/IDescribedComponent.java64
6 files changed, 638 insertions, 0 deletions
diff --git a/base/src/main/java/bjc/utils/components/ComponentDescription.java b/base/src/main/java/bjc/utils/components/ComponentDescription.java
new file mode 100644
index 0000000..28f81d1
--- /dev/null
+++ b/base/src/main/java/bjc/utils/components/ComponentDescription.java
@@ -0,0 +1,135 @@
+package bjc.utils.components;
+
+/**
+ * Generic implementation of a description for a component
+ *
+ * @author ben
+ *
+ */
+public class ComponentDescription implements IDescribedComponent {
+ private static void sanityCheckArgs(final String name, final String author, final String description,
+ final int version) {
+ if (name == null)
+ throw new NullPointerException("Component name can't be null");
+ else if (version <= 0) throw new IllegalArgumentException("Component version must be greater than 0");
+ }
+
+ /**
+ * The author of the component
+ */
+ private final String author;
+ /**
+ * The description of the component
+ */
+ private final String description;
+ /**
+ * The name of the component
+ */
+ private final String name;
+
+ /**
+ * The version of the component
+ */
+ private final int version;
+
+ /**
+ * Create a new component description
+ *
+ * @param name
+ * The name of the component
+ * @param author
+ * The author of the component
+ * @param description
+ * The description of the component
+ * @param version
+ * The version of the component
+ * @throws IllegalArgumentException
+ * thrown if version is less than 1
+ */
+ public ComponentDescription(final String name, final String author, final String description,
+ final int version) {
+ sanityCheckArgs(name, author, description, version);
+
+ this.name = name;
+ this.author = author;
+ this.description = description;
+ this.version = version;
+ }
+
+ @Override
+ public String getAuthor() {
+ if (author == null) return IDescribedComponent.super.getAuthor();
+
+ return author;
+ }
+
+ @Override
+ public String getDescription() {
+ if (description == null) return IDescribedComponent.super.getDescription();
+
+ return description;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int getVersion() {
+ return version;
+ }
+
+ @Override
+ public String toString() {
+ return name + " component v" + version + ", written by " + author;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+
+ result = prime * result + (author == null ? 0 : author.hashCode());
+ result = prime * result + (description == null ? 0 : description.hashCode());
+ result = prime * result + (name == null ? 0 : name.hashCode());
+ result = prime * result + version;
+
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ final ComponentDescription other = (ComponentDescription) obj;
+
+ if (author == null) {
+ if (other.author != null) return false;
+ } else if (!author.equals(other.author)) return false;
+
+ if (description == null) {
+ if (other.description != null) return false;
+ } else if (!description.equals(other.description)) return false;
+
+ if (name == null) {
+ if (other.name != null) return false;
+ } else if (!name.equals(other.name)) return false;
+
+ if (version != other.version) return false;
+
+ return true;
+ }
+}
diff --git a/base/src/main/java/bjc/utils/components/ComponentDescriptionFileParser.java b/base/src/main/java/bjc/utils/components/ComponentDescriptionFileParser.java
new file mode 100644
index 0000000..f7ddaff
--- /dev/null
+++ b/base/src/main/java/bjc/utils/components/ComponentDescriptionFileParser.java
@@ -0,0 +1,65 @@
+package bjc.utils.components;
+
+import static bjc.utils.ioutils.RuleBasedReaderPragmas.buildInteger;
+import static bjc.utils.ioutils.RuleBasedReaderPragmas.buildStringCollapser;
+
+import java.io.InputStream;
+
+import bjc.utils.ioutils.RuleBasedConfigReader;
+
+/**
+ * Read a component description from a file
+ *
+ * @author ben
+ *
+ */
+public class ComponentDescriptionFileParser {
+ // The reader used to read in component descriptions
+ private static RuleBasedConfigReader<ComponentDescriptionState> reader;
+
+ // Initialize the reader and its pragmas
+ static {
+ // This reader works entirely off of pragmas, so no need to
+ // handle
+ // rules
+ reader = new RuleBasedConfigReader<>((tokenizer, statePair) -> {
+ // Don't need to do anything on rule start
+ }, (tokenizer, state) -> {
+ // Don't need to do anything on rule continuation
+ }, (state) -> {
+ // Don't need to do anything on rule end
+ });
+
+ setupReaderPragmas();
+ }
+
+ /**
+ * Parse a component description from a stream
+ *
+ * @param inputSource
+ * The stream to parse from
+ * @return The description parsed from the stream
+ */
+ public static ComponentDescription fromStream(final InputStream inputSource) {
+ if (inputSource == null) throw new NullPointerException("Input source must not be null");
+
+ final ComponentDescriptionState readState = reader.fromStream(inputSource,
+ new ComponentDescriptionState());
+
+ return readState.toDescription();
+ }
+
+ /*
+ * Create all the pragmas the reader needs to function
+ */
+ private static void setupReaderPragmas() {
+ reader.addPragma("name", buildStringCollapser("name", (name, state) -> state.setName(name)));
+
+ reader.addPragma("author", buildStringCollapser("author", (author, state) -> state.setAuthor(author)));
+
+ reader.addPragma("description", buildStringCollapser("description",
+ (description, state) -> state.setDescription(description)));
+
+ reader.addPragma("version", buildInteger("version", (version, state) -> state.setVersion(version)));
+ }
+}
diff --git a/base/src/main/java/bjc/utils/components/ComponentDescriptionState.java b/base/src/main/java/bjc/utils/components/ComponentDescriptionState.java
new file mode 100644
index 0000000..8d66f85
--- /dev/null
+++ b/base/src/main/java/bjc/utils/components/ComponentDescriptionState.java
@@ -0,0 +1,144 @@
+package bjc.utils.components;
+
+/**
+ * Internal state of component description parser
+ *
+ * @author ben
+ *
+ */
+public class ComponentDescriptionState {
+ // Tentative name of this component
+ private String name;
+
+ // Tentative description of this componet
+ private String description;
+
+ // Tentative author of this component
+ private String author;
+
+ // Tentative version of this component
+ private int version;
+
+ /**
+ * Set the author of this component
+ *
+ * @param author
+ * The author of this component
+ */
+ public void setAuthor(final String author) {
+ this.author = author;
+ }
+
+ /**
+ * Set the description of this component
+ *
+ * @param description
+ * The description of this component
+ */
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ /**
+ * Set the name of this component
+ *
+ * @param name
+ * The name of this component
+ */
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * Set the version of this component
+ *
+ * @param version
+ * The version of this component
+ */
+ public void setVersion(final int version) {
+ this.version = version;
+ }
+
+ /**
+ * Convert this state into the description it represents
+ *
+ * @return The description represented by this state
+ */
+ public ComponentDescription toDescription() {
+ return new ComponentDescription(name, author, description, version);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+
+ result = prime * result + (author == null ? 0 : author.hashCode());
+ result = prime * result + (description == null ? 0 : description.hashCode());
+ result = prime * result + (name == null ? 0 : name.hashCode());
+ result = prime * result + version;
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+
+ final ComponentDescriptionState other = (ComponentDescriptionState) obj;
+
+ if (author == null) {
+ if (other.author != null) return false;
+ } else if (!author.equals(other.author)) return false;
+
+ if (description == null) {
+ if (other.description != null) return false;
+ } else if (!description.equals(other.description)) return false;
+
+ if (name == null) {
+ if (other.name != null) return false;
+ } else if (!name.equals(other.name)) return false;
+
+ if (version != other.version) return false;
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("ComponentDescriptionState [");
+
+ if (name != null) {
+ builder.append("name=");
+ builder.append(name);
+ builder.append(", ");
+ }
+
+ if (description != null) {
+ builder.append("description=");
+ builder.append(description);
+ builder.append(", ");
+ }
+
+ if (author != null) {
+ builder.append("author=");
+ builder.append(author);
+ builder.append(", ");
+ }
+
+ builder.append("version=");
+ builder.append(version);
+ builder.append("]");
+
+ return builder.toString();
+ }
+
+}
diff --git a/base/src/main/java/bjc/utils/components/FileComponentRepository.java b/base/src/main/java/bjc/utils/components/FileComponentRepository.java
new file mode 100644
index 0000000..efde5c7
--- /dev/null
+++ b/base/src/main/java/bjc/utils/components/FileComponentRepository.java
@@ -0,0 +1,181 @@
+package bjc.utils.components;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import bjc.utils.data.IHolder;
+import bjc.utils.data.Identity;
+import bjc.utils.funcdata.FunctionalMap;
+import bjc.utils.funcdata.IList;
+import bjc.utils.funcdata.IMap;
+import bjc.utils.funcutils.FileUtils;
+
+/**
+ * A component repository that loads its components from files in a directory
+ *
+ * @author ben
+ *
+ * @param <ComponentType>
+ * The type of component being read in
+ */
+public class FileComponentRepository<ComponentType extends IDescribedComponent>
+ implements IComponentRepository<ComponentType> {
+ // The logger to use for storing data about this class
+ private static final Logger CLASS_LOGGER = Logger.getLogger("FileComponentRepository");
+
+ // The internal storage of components
+ private IMap<String, ComponentType> components;
+
+ // The path that all the components came from
+ private Path sourceDirectory;
+
+ /**
+ * Create a new component repository sourcing components from files in a
+ * directory
+ *
+ * An exception thrown during the loading of a component will only cause
+ * the loading of that component to fail, but a warning will be logged.
+ *
+ * @param directory
+ * The directory to read component files from
+ * @param componentReader
+ * The function to use to convert files to components
+ */
+ public FileComponentRepository(final File directory,
+ final Function<File, ? extends ComponentType> componentReader) {
+ // Make sure we have valid arguments
+ if (directory == null)
+ throw new NullPointerException("Directory must not be null");
+ else if (!directory.isDirectory())
+ throw new IllegalArgumentException("File " + directory + " is not a directory.\n"
+ + "Components can only be read from a directory");
+ else if (componentReader == null) throw new NullPointerException("Component reader must not be null");
+
+ // Initialize our fields
+ components = new FunctionalMap<>();
+ sourceDirectory = directory.toPath().toAbsolutePath();
+
+ // Marker for making sure we don't skip the parent
+ final IHolder<Boolean> isFirstDir = new Identity<>(true);
+
+ // Predicate to use to traverse all the files in a directory,
+ // but
+ // not recurse into sub-directories
+ final BiPredicate<Path, BasicFileAttributes> firstLevelTraverser = (pth, attr) -> {
+ if (attr.isDirectory() && !isFirstDir.getValue()) /*
+ * Skip
+ * directories,
+ * they
+ * probably
+ * have
+ * component
+ * support
+ * files.
+ */
+ return false;
+
+ /*
+ * Don't skip the first directory, that's the parent
+ * directory
+ */
+ isFirstDir.replace(false);
+
+ return true;
+ };
+
+ // Try reading components
+ try {
+ FileUtils.traverseDirectory(sourceDirectory, firstLevelTraverser, (pth, attr) -> {
+ loadComponent(componentReader, pth);
+
+ // Keep loading components, even if this one
+ // failed
+ return true;
+ });
+ } catch (final IOException ioex) {
+ CLASS_LOGGER.log(Level.WARNING, ioex, () -> "Error found reading component from file.");
+ }
+ }
+
+ @Override
+ public IMap<String, ComponentType> getAll() {
+ return components;
+ }
+
+ @Override
+ public ComponentType getByName(final String name) {
+ return components.get(name);
+ }
+
+ @Override
+ public IList<ComponentType> getList() {
+ return components.valueList();
+ }
+
+ @Override
+ public String getSource() {
+ return "Components read from directory " + sourceDirectory + ".";
+ }
+
+ /*
+ * Load a component from a file
+ */
+ private void loadComponent(final Function<File, ? extends ComponentType> componentReader, final Path pth) {
+ try {
+ // Try to load the component
+ final ComponentType component = componentReader.apply(pth.toFile());
+
+ if (component == null)
+ throw new NullPointerException("Component reader read null component");
+ else if (!components.containsKey(component.getName())) {
+ // We only care about the latest version of a
+ // component
+ final ComponentType oldComponent = components.put(component.getName(), component);
+
+ if (oldComponent.getVersion() > component.getVersion()) {
+ components.put(oldComponent.getName(), oldComponent);
+ }
+ } else {
+ CLASS_LOGGER.warning("Found a duplicate component.\n"
+ + "Multiple versions of the same component are not currently supported.\n"
+ + "Only the latest version of the component" + component
+ + " will be registered .");
+ }
+ } catch (final Exception ex) {
+ CLASS_LOGGER.log(Level.WARNING, ex, () -> "Error found reading component from file "
+ + pth.toString() + ". This component will not be loaded");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("FileComponentRepository [");
+
+ if (components != null) {
+ builder.append("components=");
+ builder.append(components);
+ builder.append(", ");
+ }
+
+ if (sourceDirectory != null) {
+ builder.append("sourceDirectory=");
+ builder.append(sourceDirectory);
+ }
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/bjc/utils/components/IComponentRepository.java b/base/src/main/java/bjc/utils/components/IComponentRepository.java
new file mode 100644
index 0000000..6ee51f3
--- /dev/null
+++ b/base/src/main/java/bjc/utils/components/IComponentRepository.java
@@ -0,0 +1,49 @@
+package bjc.utils.components;
+
+import bjc.utils.funcdata.IList;
+import bjc.utils.funcdata.IMap;
+
+/**
+ * A collection of implementations of a particular type of
+ * {@link IDescribedComponent}
+ *
+ * @author ben
+ *
+ * @param <ComponentType>
+ * The type of components contained in this repository
+ */
+public interface IComponentRepository<ComponentType extends IDescribedComponent> {
+ /**
+ * Get all of the components this repository knows about
+ *
+ * @return A map from component name to component, containing all of the
+ * components in the repositories
+ */
+ public IMap<String, ComponentType> getAll();
+
+ /**
+ * Get a component with a specific name
+ *
+ * @param name
+ * The name of the component to retrieve
+ * @return The named component, or null if no component with that name
+ * exists
+ */
+ public ComponentType getByName(String name);
+
+ /**
+ * Get a list of all the registered components
+ *
+ * @return A list of all the registered components
+ */
+ public default IList<ComponentType> getList() {
+ return getAll().valueList();
+ }
+
+ /**
+ * Get the source from which these components came
+ *
+ * @return The source from which these components came
+ */
+ public String getSource();
+} \ No newline at end of file
diff --git a/base/src/main/java/bjc/utils/components/IDescribedComponent.java b/base/src/main/java/bjc/utils/components/IDescribedComponent.java
new file mode 100644
index 0000000..952b375
--- /dev/null
+++ b/base/src/main/java/bjc/utils/components/IDescribedComponent.java
@@ -0,0 +1,64 @@
+package bjc.utils.components;
+
+/**
+ * Represents a optional component that has status information associated with
+ * it
+ *
+ * @author ben
+ *
+ */
+public interface IDescribedComponent extends Comparable<IDescribedComponent> {
+ /**
+ * Get the author of this component
+ *
+ * Providing this is optional, with "Anonymous" as the default author
+ *
+ * @return The author of the component
+ */
+ default String getAuthor() {
+ return "Anonymous";
+ }
+
+ /**
+ * Get the description of this component
+ *
+ * Providing this is optional, with the default being a note that no
+ * description was provided
+ *
+ * @return The description of the component
+ */
+ default String getDescription() {
+ return "No description provided.";
+ }
+
+ /**
+ * Get the name of this component.
+ *
+ * This is the only thing required of all components
+ *
+ * @return The name of the component
+ */
+ String getName();
+
+ /**
+ * Get the version of this component
+ *
+ * Providing this is optional, with "1" as the default version
+ *
+ * @return The version of this component
+ */
+ default int getVersion() {
+ return 1;
+ }
+
+ @Override
+ default int compareTo(final IDescribedComponent o) {
+ int res = getName().compareTo(o.getName());
+
+ if (res == 0) {
+ res = getVersion() - o.getVersion();
+ }
+
+ return res;
+ }
+} \ No newline at end of file