From c82e3b3b2de0633317ec8fc85925e91422820597 Mon Sep 17 00:00:00 2001 From: "Benjamin J. Culkin" Date: Sun, 8 Oct 2017 22:39:59 -0300 Subject: Start splitting into maven modules --- .../bjc/utils/components/ComponentDescription.java | 135 +++++++++++++++ .../components/ComponentDescriptionFileParser.java | 65 ++++++++ .../components/ComponentDescriptionState.java | 144 ++++++++++++++++ .../utils/components/FileComponentRepository.java | 181 +++++++++++++++++++++ .../bjc/utils/components/IComponentRepository.java | 49 ++++++ .../bjc/utils/components/IDescribedComponent.java | 64 ++++++++ 6 files changed, 638 insertions(+) create mode 100644 base/src/main/java/bjc/utils/components/ComponentDescription.java create mode 100644 base/src/main/java/bjc/utils/components/ComponentDescriptionFileParser.java create mode 100644 base/src/main/java/bjc/utils/components/ComponentDescriptionState.java create mode 100644 base/src/main/java/bjc/utils/components/FileComponentRepository.java create mode 100644 base/src/main/java/bjc/utils/components/IComponentRepository.java create mode 100644 base/src/main/java/bjc/utils/components/IDescribedComponent.java (limited to 'base/src/main/java/bjc/utils/components') 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 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 + * The type of component being read in + */ +public class FileComponentRepository + implements IComponentRepository { + // 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 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 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 isFirstDir = new Identity<>(true); + + // Predicate to use to traverse all the files in a directory, + // but + // not recurse into sub-directories + final BiPredicate 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 getAll() { + return components; + } + + @Override + public ComponentType getByName(final String name) { + return components.get(name); + } + + @Override + public IList 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 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 + * The type of components contained in this repository + */ +public interface IComponentRepository { + /** + * 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 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 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 { + /** + * 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 -- cgit v1.2.3