diff --git a/builders/build.gradle b/builders/build.gradle new file mode 100644 index 0000000..445fbd4 --- /dev/null +++ b/builders/build.gradle @@ -0,0 +1,57 @@ +plugins { + id 'java-library' + id 'jacoco' +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + withJavadocJar() + withSourcesJar() +} + +dependencies { + testImplementation 'junit:junit:4.13.2' +} + +publishing { + publications { + maven(MavenPublication) { + groupId = "io.github.dk96-os.files-jvm" + artifactId = "builders" + apply from: "../artifacts.gradle" + version = artifactVersions.visitors + from components.java + } + } +} + +tasks.test { + maxParallelForks = 3 +} + +tasks.jacocoTestReport { + dependsOn tasks.test + reports { + xml.required = false + csv.required = false + html.outputLocation = layout.buildDirectory.dir("jacocoReport") + } +} + +tasks.jacocoTestCoverageVerification { + dependsOn tasks.test + violationRules { + failOnViolation = true + rule { + limit { + counter = "INSTRUCTION" + minimum = 0.960 + } + limit { + counter = "BRANCH" + minimum = 0.850 + } + } + } +} \ No newline at end of file diff --git a/builders/src/main/java/files/jvm/builders/ProceduralBuilder.java b/builders/src/main/java/files/jvm/builders/ProceduralBuilder.java new file mode 100644 index 0000000..e3c7dd2 --- /dev/null +++ b/builders/src/main/java/files/jvm/builders/ProceduralBuilder.java @@ -0,0 +1,21 @@ +package files.jvm.builders; + +import java.util.Stack; + +import files.jvm.builders.input.InputLineReader; + +/** The Builder that follows a strict logical procedure. + */ +public final class ProceduralBuilder { + + final InputLineReader reader = new InputLineReader(); + + final Stack pathStack = new Stack<>(); + + /** Constructor. + */ + public ProceduralBuilder() { + pathStack.add("."); + } + +} \ No newline at end of file diff --git a/builders/src/main/java/files/jvm/builders/data/TreeNodeRecord.java b/builders/src/main/java/files/jvm/builders/data/TreeNodeRecord.java new file mode 100644 index 0000000..9c3d062 --- /dev/null +++ b/builders/src/main/java/files/jvm/builders/data/TreeNodeRecord.java @@ -0,0 +1,10 @@ +package files.jvm.builders.data; + +/** The key data points for a Tree Node. + */ +public record TreeNodeRecord( + int depth, + boolean isDirectory, + String name, + String data +) {} \ No newline at end of file diff --git a/builders/src/main/java/files/jvm/builders/input/InputCharacters.java b/builders/src/main/java/files/jvm/builders/input/InputCharacters.java new file mode 100644 index 0000000..61cb6c2 --- /dev/null +++ b/builders/src/main/java/files/jvm/builders/input/InputCharacters.java @@ -0,0 +1,56 @@ +package files.jvm.builders.input; + +import java.util.Arrays; + +/** Manages the Key Characters found in valid Inputs. + */ +public class InputCharacters { + + final int[] SpaceChars = new int[]{' ', ' ', ' ', ' '}; + + final int[] DirectoryChars = new int[]{'/', '\\'}; + + /** Determines if this string contains any directory characters. + * @param name The Name of the Node. May contain directory characters. + * @return Whether the name contained any directory characters. + */ + public boolean isDirectory( + final String name + ) { + final boolean containsCharacters = name.chars() + .anyMatch(i -> + Arrays.stream(DirectoryChars).anyMatch(it -> it == i) + ); + if (!containsCharacters) + return false; + // todo: Check that the character is at the start or end of the name + return true; + } + + /** Count the number of space characters at the start of the + * @param line The input line to count spaces. + * @return The number of consecutive space characters at the start of the input. + */ + public long countStartingSpaces( + final String line + ) { + return line.chars() + .takeWhile(i -> Arrays.binarySearch(SpaceChars, i) >= 0) + .count(); + } + + /** Obtain a Space Character value from the internal collection of accepted values. + * @param index The index of the character in the collection. + * @return The Char value, representing a space. + */ + public char getSpaceCharAt( + final int index + ) { + char space = (char) SpaceChars[0]; + if (0 < index && index < SpaceChars.length) { + space = (char) SpaceChars[index]; + } + return space; + } + +} \ No newline at end of file diff --git a/builders/src/main/java/files/jvm/builders/input/InputLineReader.java b/builders/src/main/java/files/jvm/builders/input/InputLineReader.java new file mode 100644 index 0000000..69a8597 --- /dev/null +++ b/builders/src/main/java/files/jvm/builders/input/InputLineReader.java @@ -0,0 +1,125 @@ +package files.jvm.builders.input; + +import java.util.List; +import java.util.Objects; + +import files.jvm.builders.data.TreeNodeRecord; + +/** The Default Input Line Reader. + */ +public final class InputLineReader { + + final InputCharacters mCharacters = new InputCharacters(); + + /** Calculates the depth of a line in the tree structure. + * @param line A line from the tree command output. + * @return The depth of the line in the tree structure. + */ + short calculateDepth( + final String line + ) { + long spaceCount = mCharacters.countStartingSpaces(line); + final short depth = (short) (spaceCount >>> 1); + if (depth << 1 == spaceCount) + return depth; + throw new IllegalArgumentException("Invalid Spacing in Line"); + } + + /** Creates a string of space chars equivalent to the given depth. + * Default is the first space in the list of accepted spaces. + * @param depth The amount of depth in the Tree Node Structure. + * @return The depth of the line in the tree structure. + */ + String createDepth( + int depth + ) { + return createDepth(depth, 0); + } + + /** Creates a string of space chars equivalent to the given depth. + * @param depth The amount of depth in the Tree Node Structure. + * @param spaceChar The specific whitespace character to use. + * @return The depth of the line in the tree structure. + */ + String createDepth( + int depth, + int spaceChar + ) { + final char space = mCharacters.getSpaceCharAt(spaceChar); + if (depth < 2) { + if (depth == 1) return String.valueOf(new char[]{space, space}); + return ""; + } + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < depth; ++i) { + builder.append(space); + builder.append(space); + } + return builder.toString(); + } + + /** Divide the Input into multiple Arguments. + * @param input The String to check for Arguments. + * @return A List containing one or more Arguments. + */ + List splitArguments( + final String input + ) { + List args = null; + // Search for multiple arguments + for (int ch : mCharacters.SpaceChars) { + int charIndex = input.indexOf((char) ch); + if (charIndex < 0) + continue; + // Split in two at this index + args = List.of( + input.substring(0, charIndex), + input.substring(charIndex + 1).stripLeading() + ); + break; + } + return Objects.requireNonNullElse(args, List.of(input)); + } + + /** Process A Line from the Input Data. + * @param line The Line to process. + * @return A TreeNodeRecord of the Line Data, or null. + */ + public TreeNodeRecord processLine( + final String line + ) { + final short depth; + boolean isDirectory; + String name; + String data; + // Search for multiple arguments + final List args = splitArguments(line.trim()); + // Count The Arguments + if (args.size() == 0) + return null; + // First arg is the Node Name + name = args.get(0); + isDirectory = mCharacters.isDirectory(name); + // Check if name needs to be fixed + if (isDirectory) { + // Remove all Directory chars + for (int ch : mCharacters.DirectoryChars) { + name = name.replace( + String.valueOf((char) ch), "" + ); + } + } + // Calculate depth after arg valid, because it throws on odd space counts + depth = calculateDepth(line); + // Check Arguments for Data + data = args.size() == 1 ? "" : args.get(1); + // Create Data Object + return new TreeNodeRecord( + depth, + isDirectory, + name, + data + ); + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index f965653..1b0e29f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,5 +12,6 @@ pluginManagement { } rootProject.name = "files-jvm" include( + ":builders", ":visitors", )