From 338d172893ba96a658999f534bdf5a57a50fa637 Mon Sep 17 00:00:00 2001 From: ulic-youthlic Date: Mon, 12 May 2025 21:53:11 +0800 Subject: [PATCH] add stylecheck and spotbugs --- app/build.gradle.kts | 8 + app/checkstyle.xml | 447 ++++++++++++++ app/src/main/java/fun/youthlic/Graph.java | 562 +++++++++-------- app/src/main/java/fun/youthlic/GraphCLI.java | 601 ++++++++++--------- flake.nix | 1 + gradlew.bat | 188 +++--- 6 files changed, 1168 insertions(+), 639 deletions(-) create mode 100644 app/checkstyle.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6139a03..aeb7ee9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,6 +8,7 @@ plugins { // Apply the application plugin to add support for building a CLI application in Java. application + checkstyle id("com.gradleup.shadow") version "9.0.0-beta12" } @@ -16,6 +17,13 @@ repositories { mavenCentral() } +sourceSets { main { java { srcDirs("src/main/java") } } } + +checkstyle { + toolVersion = "10.12.4" + configFile = file("./checkstyle.xml") +} + dependencies { // Use JUnit Jupiter for testing. testImplementation(libs.junit.jupiter) diff --git a/app/checkstyle.xml b/app/checkstyle.xml new file mode 100644 index 0000000..97e8e9e --- /dev/null +++ b/app/checkstyle.xml @@ -0,0 +1,447 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/fun/youthlic/Graph.java b/app/src/main/java/fun/youthlic/Graph.java index aa6d303..92df719 100644 --- a/app/src/main/java/fun/youthlic/Graph.java +++ b/app/src/main/java/fun/youthlic/Graph.java @@ -12,289 +12,353 @@ import java.util.PriorityQueue; import java.util.Random; import java.util.function.Function; +/** + * Core class for impl graph. + */ public class Graph { - public record EdgeRecord(String node1, String node2, int weight) { + private final Map nodes; + private Map pageRanks; + + /** + * Initial func of Graph. + * + * @param input input text + * @param d d of page rank + */ + public Graph(final String input, final double d) { + nodes = new HashMap<>(); + parse(input); + initRageRanks(d); + } + + /** + * Initial func of Graph with default d. + * + * @param input input text + */ + public Graph(final String input) { + nodes = new HashMap<>(); + parse(input); + initRageRanks(0.85); + } + + /** + * Find bridge words. + * + * @param word1 first word + * @param word2 second word + * @return list of bridge words + */ + public Optional> findBridgeWords(final String word1, final String word2) { + final Node node1 = nodes.get(word1.toLowerCase(Locale.ENGLISH)); + final Node node2 = nodes.get(word2.toLowerCase(Locale.ENGLISH)); + + if (node1 == null || node2 == null) { + return Optional.empty(); } - private static class Node { + final var bridgeWords = new ArrayList(); + for (final var node : node1.edges.keySet()) { + if (node.edges.containsKey(node2)) { + bridgeWords.add(node.id); + } + } + return Optional.of(bridgeWords); + } - String id; - Map edges; + /** + * Find the shortest path between two words. + * + * @param word1 first word + * @param word2 second word + * @return list of the words in the path + */ + public Optional> findShortestPath(final String word1, final String word2) { + final var startWord = word1.toLowerCase(Locale.ENGLISH); + final var endWord = word2.toLowerCase(Locale.ENGLISH); + final var path = new ArrayList(); - Node(final String id) { - this.id = id; - this.edges = new HashMap<>(); - } - - void addEdge(final Node target) { - edges.put(target, edges.getOrDefault(target, 0) + 1); - } - - List getDOTEdges(final Function callback) { - final var dotEdges = new ArrayList(); - for (final var entry : edges.entrySet()) { - final Node target = entry.getKey(); - final int weight = entry.getValue(); - dotEdges.add( - callback.apply(new EdgeRecord(id, target.id, weight)) + ";\n"); - } - return dotEdges; - } - - List getDOTEdges() { - final var dotEdges = new ArrayList(); - for (final var entry : edges.entrySet()) { - final Node target = entry.getKey(); - final int weight = entry.getValue(); - dotEdges.add( - String.format("%s -> %s [label=\"%d\"];\n", id, target.id, weight)); - } - return dotEdges; - } + if (!nodes.containsKey(startWord) || !nodes.containsKey(endWord)) { + return Optional.empty(); } - private final Map nodes; + final Node startNode = nodes.get(startWord); + final Node endNode = nodes.get(endWord); - private Map pageRanks; - - public Graph(final String input, final double d) { - nodes = new HashMap<>(); - parse(input); - initRageRanks(d); + if (startNode == endNode) { + path.add(startNode.id); + return Optional.of(path); } - public Graph(final String input) { - nodes = new HashMap<>(); - parse(input); - initRageRanks(0.85); + final var distances = new HashMap(); + final var previousNodes = new HashMap(); + final var queue = new PriorityQueue( + Comparator.comparingInt(node -> distances.getOrDefault(node, Integer.MAX_VALUE))); + + for (final var node : nodes.values()) { + distances.put(node, Integer.MAX_VALUE); + } + distances.put(startNode, 0); + queue.add(startNode); + + while (!queue.isEmpty()) { + final Node current = queue.poll(); + for (final var edge : current.edges.entrySet()) { + final Node neighbor = edge.getKey(); + final int weight = edge.getValue(); + + final int newDist = distances.get(current) + weight; + if (newDist < distances.get(neighbor)) { + distances.put(neighbor, newDist); + previousNodes.put(neighbor, current); + queue.add(neighbor); + } + } } - public Optional> findBridgeWords(final String word1, final String word2) { - final Node node1 = nodes.get(word1.toLowerCase(Locale.ENGLISH)); - final Node node2 = nodes.get(word2.toLowerCase(Locale.ENGLISH)); - - if (node1 == null || node2 == null) { - return Optional.empty(); - } - - final var bridgeWords = new ArrayList(); - for (final var node : node1.edges.keySet()) { - if (node.edges.containsKey(node2)) { - bridgeWords.add(node.id); - } - } - return Optional.of(bridgeWords); + if (distances.get(endNode) == Integer.MAX_VALUE) { + return Optional.of(path); } - public Optional> findShortestPath(final String word1, final String word2) { - final var startWord = word1.toLowerCase(Locale.ENGLISH); - final var endWord = word2.toLowerCase(Locale.ENGLISH); - final var path = new ArrayList(); - - if (!nodes.containsKey(startWord) || !nodes.containsKey(endWord)) { - return Optional.empty(); - } - - final Node startNode = nodes.get(startWord); - final Node endNode = nodes.get(endWord); - - if (startNode == endNode) { - path.add(startNode.id); - return Optional.of(path); - } - - final var distances = new HashMap(); - final var previousNodes = new HashMap(); - final var queue = new PriorityQueue( - Comparator.comparingInt(node -> distances.getOrDefault(node, Integer.MAX_VALUE))); - - for (final var node : nodes.values()) { - distances.put(node, Integer.MAX_VALUE); - } - distances.put(startNode, 0); - queue.add(startNode); - - while (!queue.isEmpty()) { - final Node current = queue.poll(); - for (final var edge : current.edges.entrySet()) { - final Node neighbor = edge.getKey(); - final int weight = edge.getValue(); - - final int newDist = distances.get(current) + weight; - if (newDist < distances.get(neighbor)) { - distances.put(neighbor, newDist); - previousNodes.put(neighbor, current); - queue.add(neighbor); - } - } - } - - if (distances.get(endNode) == Integer.MAX_VALUE) { - return Optional.of(path); - } - - final var reversePath = new LinkedList(); - Node current = endNode; - while (current != null) { - reversePath.addFirst(current.id); - current = previousNodes.get(current); - } - - return Optional.of(reversePath); + final var reversePath = new LinkedList(); + Node current = endNode; + while (current != null) { + reversePath.addFirst(current.id); + current = previousNodes.get(current); } - public double computePageRank(final String nodeId) { - final Node node = nodes.get(nodeId); - if (node == null) { - return -1.0; - } + return Optional.of(reversePath); + } - return pageRanks.getOrDefault(node, -1.0); + /** + * Calculate rage rank. + * + * @param nodeId id of node + * @return page rank + */ + public double computePageRank(final String nodeId) { + final Node node = nodes.get(nodeId); + if (node == null) { + return -1.0; } - public List randomWalk() { - final var path = new ArrayList(); - if (nodes.isEmpty()) { - return path; - } + return pageRanks.getOrDefault(node, -1.0); + } - final var random = new Random(); - final var nodeIds = new ArrayList<>(nodes.keySet()); - final String startNodeId = nodeIds.get(random.nextInt(nodeIds.size())); - path.add(startNodeId); - - final var usedEdges = new HashMap(); - var currentNode = nodes.get(startNodeId); - while (true) { - final var availableEdge = new ArrayList>(); - int totalWeight = 0; - for (final var entry : currentNode.edges.entrySet()) { - final String edgeKey = currentNode.id + " -> " + entry.getKey().id; - if (usedEdges.getOrDefault(edgeKey, 0) < 2) { - availableEdge.add(entry); - totalWeight += entry.getValue(); - } - } - - if (availableEdge.isEmpty()) { - break; - } - - final double randomValue = random.nextDouble() * totalWeight; - double cumulative = 0.0; - Node nextNode = null; - - for (final var entry : availableEdge) { - cumulative += entry.getValue(); - if (randomValue < cumulative) { - nextNode = entry.getKey(); - break; - } - } - - assert nextNode != null; - final String edgeKey = currentNode.id + " -> " + nextNode.id; - final int edgeTimes = usedEdges.getOrDefault(edgeKey, 0) + 1; - usedEdges.put(edgeKey, edgeTimes); - path.add(nextNode.id); - currentNode = nextNode; - - if (edgeTimes >= 2) { - break; - } - } - return path; + /** + * Random walk. + * + * @return list of words in the walk path + */ + @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("PREDICTABLE_RANDOM") + public List randomWalk() { + final var path = new ArrayList(); + if (nodes.isEmpty()) { + return path; } - public String toDOT(final Function callback) { - final var sb = new StringBuilder(); - sb.append("digraph G {\n"); - for (final Node node : nodes.values()) { - for (final String edge : node.getDOTEdges(callback)) { - sb.append(" ").append(edge); - } + final var random = new Random(); + final var nodeIds = new ArrayList<>(nodes.keySet()); + final String startNodeId = nodeIds.get(random.nextInt(nodeIds.size())); + path.add(startNodeId); + + final var usedEdges = new HashMap(); + var currentNode = nodes.get(startNodeId); + while (true) { + final var availableEdge = new ArrayList>(); + int totalWeight = 0; + for (final var entry : currentNode.edges.entrySet()) { + final String edgeKey = currentNode.id + " -> " + entry.getKey().id; + if (usedEdges.getOrDefault(edgeKey, 0) < 2) { + availableEdge.add(entry); + totalWeight += entry.getValue(); } - sb.append("}"); - return sb.toString(); + } + + if (availableEdge.isEmpty()) { + break; + } + + final double randomValue = random.nextDouble() * totalWeight; + double cumulative = 0.0; + Node nextNode = null; + + for (final var entry : availableEdge) { + cumulative += entry.getValue(); + if (randomValue < cumulative) { + nextNode = entry.getKey(); + break; + } + } + + assert nextNode != null; + final String edgeKey = currentNode.id + " -> " + nextNode.id; + final int edgeTimes = usedEdges.getOrDefault(edgeKey, 0) + 1; + usedEdges.put(edgeKey, edgeTimes); + path.add(nextNode.id); + currentNode = nextNode; + + if (edgeTimes >= 2) { + break; + } + } + return path; + } + + /** + * Generate DOT lang to describe the graph. + * + * @param callback function to modify the option of the node + * @return string of DOT lang + */ + @SuppressWarnings("checkstyle:AbbreviationAsWordInName") + public String toDOT(final Function callback) { + final var sb = new StringBuilder(); + sb.append("digraph G {\n"); + for (final Node node : nodes.values()) { + for (final String edge : node.getDOTEdges(callback)) { + sb.append(" ").append(edge); + } + } + sb.append("}"); + return sb.toString(); + } + + /** + * Generate DOT lang to describe the graph without callback lambda function. + * + * @return string of DOT lang + */ + @SuppressWarnings("checkstyle:AbbreviationAsWordInName") + public String toDOT() { + final var sb = new StringBuilder(); + sb.append("digraph G {\n"); + for (final Node node : nodes.values()) { + for (final String edge : node.getDOTEdges()) { + sb.append(" ").append(edge); + } + } + sb.append("}"); + return sb.toString(); + } + + private void parse(final String input) { + final String[] tokens = input.toLowerCase(Locale.ENGLISH).split("[ \n\r]+"); + final var words = new ArrayList(); + + for (final var token : tokens) { + final String word = token.replaceAll("[^a-zA-Z]", ""); + if (!word.isEmpty()) { + words.add(word); + } } - public String toDOT() { - final var sb = new StringBuilder(); - sb.append("digraph G {\n"); - for (final Node node : nodes.values()) { - for (final String edge : node.getDOTEdges()) { - sb.append(" ").append(edge); - } - } - sb.append("}"); - return sb.toString(); + for (int i = 0; i < words.size() - 1; i++) { + final String currentWord = words.get(i); + final String nextWord = words.get(i + 1); + + final Node currentNode = getOrCreateNode(currentWord); + final Node nextNode = getOrCreateNode(nextWord); + + currentNode.addEdge(nextNode); + } + } + + private Node getOrCreateNode(final String word) { + return nodes.computeIfAbsent(word, Node::new); + } + + private void initRageRanks(final double d) { + final int totalNodes = nodes.size(); + if (totalNodes == 0) { + pageRanks = new HashMap<>(); + return; + } + var myPageRanks = new HashMap(); + final double initRank = 1.0 / totalNodes; + for (final var n : nodes.values()) { + myPageRanks.put(n, initRank); } - private void parse(final String input) { - final String[] tokens = input.toLowerCase(Locale.ENGLISH).split("[ \n\r]+"); - final var words = new ArrayList(); + final int iterations = 100; + for (int i = 0; i < iterations; i++) { + final var newPageRanks = new HashMap(); + nodes.values().forEach(n -> newPageRanks.put(n, 0.0)); + for (final var curr : nodes.values()) { + final int totalEdges = curr.edges.size(); - for (final var token : tokens) { - final String word = token.replaceAll("[^a-zA-Z]", ""); - if (!word.isEmpty()) { - words.add(word); - } + for (final var entry : curr.edges.entrySet()) { + final Node neighbor = entry.getKey(); + final double contribution = myPageRanks.get(curr) / totalEdges; + newPageRanks.put(neighbor, newPageRanks.get(neighbor) + contribution); } + } - for (int i = 0; i < words.size() - 1; i++) { - final String currentWord = words.get(i); - final String nextWord = words.get(i + 1); - - final Node currentNode = getOrCreateNode(currentWord); - final Node nextNode = getOrCreateNode(nextWord); - - currentNode.addEdge(nextNode); - } + final double damplingTerm = (1.0 - d) / totalNodes; + nodes.values().forEach(n -> newPageRanks.put(n, damplingTerm + d * newPageRanks.get(n))); + myPageRanks = newPageRanks; } - private Node getOrCreateNode(final String word) { - return nodes.computeIfAbsent(word, Node::new); + pageRanks = myPageRanks; + for (final var node : nodes.values()) { + if (node.edges.isEmpty()) { + nodes.values().stream() + .filter(n -> !n.equals(node)) + .forEach(n -> pageRanks + .put(n, pageRanks.get(n) + pageRanks.get(node) / (totalNodes - 1))); + pageRanks.put(node, 0.0); + } + } + } + + /** + * Record of edge. + * + * @param node1 fist node + * @param node2 second node + * @param weight weight of edge + * + */ + public record EdgeRecord(String node1, String node2, int weight) { + } + + private static class Node { + + String id; + Map edges; + + Node(final String id) { + this.id = id; + this.edges = new HashMap<>(); } - private void initRageRanks(final double d) { - final int totalNodes = nodes.size(); - if (totalNodes == 0) { - pageRanks = new HashMap<>(); - return; - } - var myPageRanks = new HashMap(); - final double initRank = 1.0 / totalNodes; - for (final var n : nodes.values()) { - myPageRanks.put(n, initRank); - } - - final int iterations = 100; - for (int i = 0; i < iterations; i++) { - final var newPageRanks = new HashMap(); - nodes.values().forEach(n -> newPageRanks.put(n, 0.0)); - for (final var curr : nodes.values()) { - final int totalEdges = curr.edges.size(); - - for (final var entry : curr.edges.entrySet()) { - final Node neighbor = entry.getKey(); - final double contribution = myPageRanks.get(curr) / totalEdges; - newPageRanks.put(neighbor, newPageRanks.get(neighbor) + contribution); - } - } - - final double damplingTerm = (1.0 - d) / totalNodes; - nodes.values().forEach(n -> newPageRanks.put(n, damplingTerm + d * newPageRanks.get(n))); - myPageRanks = newPageRanks; - } - - pageRanks = myPageRanks; - for (final var node : nodes.values()) { - if (node.edges.isEmpty()) { - nodes.values().stream().filter(n -> !n.equals(node)) - .forEach(n -> pageRanks.put(n, pageRanks.get(n) + pageRanks.get(node) / (totalNodes - 1))); - pageRanks.put(node, 0.0); - } - } + void addEdge(final Node target) { + edges.put(target, edges.getOrDefault(target, 0) + 1); } + + @SuppressWarnings("checkstyle:AbbreviationAsWordInName") + List getDOTEdges(final Function callback) { + final var dotEdges = new ArrayList(); + for (final var entry : edges.entrySet()) { + final Node target = entry.getKey(); + final int weight = entry.getValue(); + dotEdges.add( + callback.apply(new EdgeRecord(id, target.id, weight)) + ";\n"); + } + return dotEdges; + } + + @SuppressWarnings("checkstyle:AbbreviationAsWordInName") + List getDOTEdges() { + final var dotEdges = new ArrayList(); + for (final var entry : edges.entrySet()) { + final Node target = entry.getKey(); + final int weight = entry.getValue(); + dotEdges.add( + String.format("%s -> %s [label=\"%d\"];%n", id, target.id, weight)); + } + return dotEdges; + } + } } diff --git a/app/src/main/java/fun/youthlic/GraphCLI.java b/app/src/main/java/fun/youthlic/GraphCLI.java index 1c6c245..a8aff4e 100644 --- a/app/src/main/java/fun/youthlic/GraphCLI.java +++ b/app/src/main/java/fun/youthlic/GraphCLI.java @@ -1,8 +1,8 @@ package fun.youthlic; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.BufferedReader; import java.io.BufferedWriter; -import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; @@ -15,324 +15,333 @@ import java.util.Arrays; import java.util.Random; import java.util.stream.Collectors; import java.util.stream.IntStream; - import org.jline.reader.LineReader; import org.jline.reader.LineReaderBuilder; import org.jline.reader.impl.DefaultParser; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; +/** + * Class for running in Commandline. + */ +@SuppressWarnings("ALL") public class GraphCLI { - static String readFile(final String filename) { - String text = null; - try (BufferedReader inputReader = new BufferedReader(new FileReader(new File(filename)))) { - text = inputReader.lines().collect(Collectors.joining(System.lineSeparator())); - } catch (final IOException e) { - System.out.println("Error reading file: " + e.getMessage()); - } - return text; + @SuppressFBWarnings({"DM_DEFAULT_ENCODING", "PATH_TRAVERSAL_IN"}) + static String readFile(final String filename) { + String text = null; + try (BufferedReader inputReader = new BufferedReader(new FileReader(filename))) { + text = inputReader.lines().collect(Collectors.joining(System.lineSeparator())); + } catch (final IOException e) { + System.out.println("Error reading file: " + e.getMessage()); } + return text; + } - public static void main(final String[] args) { - GraphCLIHelper helper = null; - Terminal terminal = null; - try { - terminal = TerminalBuilder.builder().system(true).build(); - } catch (final IOException e) { - e.printStackTrace(); - System.exit(-1); - } - final LineReader reader = LineReaderBuilder.builder().terminal(terminal).parser(new DefaultParser()) - .build(); - while (true) { - final String line = reader.readLine("graph> "); - final var commands = line.split("[ \\t]+"); - if (commands.length == 0) { - continue; - } - switch (commands[0]) { - case "load": - if (commands.length != 2) { - System.out.println("Usage: load "); - } else { - final var text = readFile(commands[1]); - if (text == null) { - System.out.println("Load file failed; " + commands[1]); - continue; - } - helper = new GraphCLIHelper(text); - } - break; - case "help": - System.out.println("Available commands:"); - System.out.println(" show"); - System.out.println(" dot"); - System.out.println(" help"); - System.out.println(" exit"); - System.out.println(" random-walk"); - System.out.println(" load "); - System.out.println(" page-rank "); - System.out.println(" bridge-words "); - System.out.println(" new-text "); - System.out.println(" shortest-path "); - break; - case "shortest-path": - if (commands.length != 3) { - System.out.println("Usage: shortest-path "); - } else if (helper == null) { - System.out.println("No graph loaded"); - break; - } else { - try { - System.out.println(helper.calcShortestPath(commands[1], commands[2])); - } catch (IOException | InterruptedException e) { - System.out.println(e.getMessage()); - } - } - break; - case "exit": - return; - case "random-walk": - if (commands.length != 1) { - System.out.println("Usage: random-walk"); - } else { - if (helper == null) { - System.out.println("No graph loaded"); - break; - } - System.out.println(helper.randomWalk()); - } - break; - case "new-text": - if (commands.length < 2) { - System.out.println("Usage: new-text "); - } else if (helper == null) { - System.out.println("No graph loaded"); - break; - } else { - System.out.println(helper.generateNewText( - String.join(" ", Arrays.asList(commands).subList(1, commands.length)))); - } - break; - case "show": - if (commands.length != 1) { - System.out.println("Usage: show"); - } else { - if (helper == null) { - System.out.println("No graph loaded"); - break; - } - GraphCLIHelper.showDirectedGraph(helper.graph); - } - break; - case "dot": - if (commands.length != 1) { - System.out.println("Usage: dot"); - } else { - if (helper == null) { - System.out.println("No graph loaded"); - break; - } - System.out.println(helper.graph.toDOT()); - } - case "page-rank": - if (commands.length != 2) { - System.out.println("Usage: page-rank "); - } else if (helper == null) { - System.out.println("No graph loaded"); - } else { - final var pageRank = helper.calPageRank(commands[1]); - if (pageRank < 0.0) { - System.out.println("Invalid word"); - } else { - System.out.println("PageRank of " + commands[1] + ": " + pageRank); - } - } - break; - case "bridge-words": - if (commands.length != 3) { - System.out.println("Usage: bridge-words "); - } else if (helper == null) { - System.out.println("No graph loaded"); - } else { - System.out.println(helper.queryBridgeWords(commands[1], commands[2])); - } - break; - default: - System.out.println("Unknown command: " + commands[0]); - break; - } - } + /** + * CommandLine entry point. + * + * @param args launch args + */ + public static void main(final String[] args) { + GraphCLIHelper helper = null; + Terminal terminal = null; + try { + terminal = TerminalBuilder.builder().system(true).build(); + } catch (final IOException e) { + e.printStackTrace(); + System.exit(-1); } + final LineReader reader = LineReaderBuilder.builder().terminal(terminal) + .parser(new DefaultParser()) + .build(); + while (true) { + final String line = reader.readLine("graph> "); + final var commands = line.split("[ \\t]+"); + if (commands.length == 0) { + continue; + } + switch (commands[0]) { + case "load": + if (commands.length != 2) { + System.out.println("Usage: load "); + } else { + final var text = readFile(commands[1]); + if (text == null) { + System.out.println("Load file failed; " + commands[1]); + continue; + } + helper = new GraphCLIHelper(text); + } + break; + case "help": + System.out.println("Available commands:"); + System.out.println(" show"); + System.out.println(" dot"); + System.out.println(" help"); + System.out.println(" exit"); + System.out.println(" random-walk"); + System.out.println(" load "); + System.out.println(" page-rank "); + System.out.println(" bridge-words "); + System.out.println(" new-text "); + System.out.println(" shortest-path "); + break; + case "shortest-path": + if (commands.length != 3) { + System.out.println("Usage: shortest-path "); + } else if (helper == null) { + System.out.println("No graph loaded"); + break; + } else { + try { + System.out.println(helper.calcShortestPath(commands[1], commands[2])); + } catch (IOException | InterruptedException e) { + System.out.println(e.getMessage()); + } + } + break; + case "exit": + return; + case "random-walk": + if (commands.length != 1) { + System.out.println("Usage: random-walk"); + } else { + if (helper == null) { + System.out.println("No graph loaded"); + break; + } + System.out.println(helper.randomWalk()); + } + break; + case "new-text": + if (commands.length < 2) { + System.out.println("Usage: new-text "); + } else if (helper == null) { + System.out.println("No graph loaded"); + break; + } else { + System.out.println(helper.generateNewText( + String.join(" ", Arrays.asList(commands).subList(1, commands.length)))); + } + break; + case "show": + if (commands.length != 1) { + System.out.println("Usage: show"); + } else { + if (helper == null) { + System.out.println("No graph loaded"); + break; + } + GraphCLIHelper.showDirectedGraph(helper.graph); + } + break; + case "dot": + if (commands.length != 1) { + System.out.println("Usage: dot"); + } else { + if (helper == null) { + System.out.println("No graph loaded"); + break; + } + System.out.println(helper.graph.toDOT()); + } + break; + case "page-rank": + if (commands.length != 2) { + System.out.println("Usage: page-rank "); + } else if (helper == null) { + System.out.println("No graph loaded"); + } else { + final var pageRank = helper.calPageRank(commands[1]); + if (pageRank < 0.0) { + System.out.println("Invalid word"); + } else { + System.out.println("PageRank of " + commands[1] + ": " + pageRank); + } + } + break; + case "bridge-words": + if (commands.length != 3) { + System.out.println("Usage: bridge-words "); + } else if (helper == null) { + System.out.println("No graph loaded"); + } else { + System.out.println(helper.queryBridgeWords(commands[1], commands[2])); + } + break; + default: + System.out.println("Unknown command: " + commands[0]); + break; + } + } + } } +/** + * Helper class for CommandLine. + */ +@SuppressWarnings("ALL") class GraphCLIHelper { - Graph graph; + Graph graph; - GraphCLIHelper(final String text) { - graph = new Graph(text); + GraphCLIHelper(final String text) { + graph = new Graph(text); + } + + static void showDirectedGraph(final Graph graph) { + final ProcessBuilder processBuilder = new ProcessBuilder("graph-easy", "--as", "ascii"); + processBuilder.redirectErrorStream(true); + Process process; + try { + process = processBuilder.start(); + } catch (final IOException e) { + System.err.println("Failed to start process"); + e.printStackTrace(); + return; } - - static void showDirectedGraph(final Graph G) { - final ProcessBuilder processBuilder = new ProcessBuilder("graph-easy", "--as", "ascii"); - processBuilder.redirectErrorStream(true); - Process process = null; - try { - process = processBuilder.start(); - } catch (final IOException e) { - System.err.println("Failed to start process"); - e.printStackTrace(); - } - try (BufferedWriter writer = new BufferedWriter( - new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8))) { - writer.write(G.toDOT()); - writer.flush(); - } catch (final IOException e) { - System.err.println("Failed to write to process"); - e.printStackTrace(); - } - final StringBuilder output = new StringBuilder(); - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { - String line; - while ((line = reader.readLine()) != null) { - output.append(line).append(System.lineSeparator()); - } - } catch (final IOException e) { - System.err.println("Failed to read from process"); - e.printStackTrace(); - } - try { - final int exitCode = process.waitFor(); - if (exitCode != 0) { - System.err.println("Process exited with code " + exitCode); - } else { - System.out.println(output.toString()); - } - } catch (final InterruptedException e) { - System.err.println("Process interrupted"); - e.printStackTrace(); - } + try (BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8))) { + writer.write(graph.toDOT()); + writer.flush(); + } catch (final IOException e) { + System.err.println("Failed to write to process"); + e.printStackTrace(); } + final StringBuilder output = new StringBuilder(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append(System.lineSeparator()); + } + } catch (final IOException e) { + System.err.println("Failed to read from process"); + e.printStackTrace(); + } + try { + final int exitCode = process.waitFor(); + if (exitCode != 0) { + System.err.println("Process exited with code " + exitCode); + } else { + System.out.println(output.toString()); + } + } catch (final InterruptedException e) { + System.err.println("Process interrupted"); + e.printStackTrace(); + } + } - String queryBridgeWords(final String word1, final String word2) { - final var option = graph.findBridgeWords(word1, word2); - if (option.isEmpty()) { - return "No " + word1 + " or " + word2 + " in the graph!"; - } - final var bridgeWords = option.get(); - if (bridgeWords.isEmpty()) { - return "No bridge words from " + word1 + " to " + word2 + "!"; - } - final var output = new StringBuilder(); - output.append("The bridge words from ").append(word1).append(" to ").append(word2).append(" are: "); - for (int i = 0; i < bridgeWords.size(); i++) { - output.append(bridgeWords.get(i)); - if (i < bridgeWords.size() - 2) { - output.append(", "); - } else if (i == bridgeWords.size() - 2) { - output.append(", and "); - } else { - output.append("."); - } + String queryBridgeWords(final String word1, final String word2) { + final var option = graph.findBridgeWords(word1, word2); + if (option.isEmpty()) { + return "No " + word1 + " or " + word2 + " in the graph!"; + } + final var bridgeWords = option.get(); + if (bridgeWords.isEmpty()) { + return "No bridge words from " + word1 + " to " + word2 + "!"; + } + final var output = new StringBuilder(); + output.append("The bridge words from ").append(word1).append(" to ").append(word2).append(" are: "); + for (int i = 0; i < bridgeWords.size(); i++) { + output.append(bridgeWords.get(i)); + if (i < bridgeWords.size() - 2) { + output.append(", "); + } else if (i == bridgeWords.size() - 2) { + output.append(", and "); + } else { + output.append("."); + } + } + return output.toString(); + } + + @SuppressFBWarnings({"PREDICTABLE_RANDOM", "DMI_RANDOM_USED_ONLY_ONCE"}) + String generateNewText(final String inputText) { + final var output = new StringBuilder(); + final var words = inputText.split("\\s+"); + for (int i = 0; i < words.length; i++) { + output.append(words[i]); + if (i < words.length - 1) { + output.append(" "); + final var option = graph.findBridgeWords(words[i], words[i + 1]); + if (option.isPresent() && !option.get().isEmpty()) { + final var bridgeWords = option.get(); + final Random random = new Random(); + final int wordIndex = random.nextInt(bridgeWords.size()); + output.append(option.get().get(wordIndex)).append(" "); } + } + } + return output.toString(); + } + + String calcShortestPath(final String word1, final String word2) + throws IOException, InterruptedException { + final ProcessBuilder processBuilder = new ProcessBuilder("graph-easy", "--as", "ascii"); + processBuilder.redirectErrorStream(true); + Process process = null; + process = processBuilder.start(); + try (BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8))) { + writer.write(calcShortestPathToDOT(word1, word2)); + writer.flush(); + } + final StringBuilder output = new StringBuilder(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append(System.lineSeparator()); + } + } + try { + final int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new IOException("Process exited with code " + exitCode); + } else { return output.toString(); + } + } catch (final InterruptedException e) { + throw e; } + } - String generateNewText(final String inputText) { - final var output = new StringBuilder(); - final var words = inputText.split("\\s+"); - for (int i = 0; i < words.length; i++) { - output.append(words[i]); - if (i < words.length - 1) { - output.append(" "); - final var option = graph.findBridgeWords(words[i], words[i + 1]); - if (option.isPresent() && !option.get().isEmpty()) { - final var bridgeWords = option.get(); - final Random random = new Random(); - final int wordIndex = random.nextInt(bridgeWords.size()); - output.append(option.get().get(wordIndex)).append(" "); - } - } - } - return output.toString(); + String calcShortestPathToDOT(final String word1, final String word2) { + var option = graph.findShortestPath(word1, word2); + if (option.isEmpty()) { + return "No " + word1 + " or " + word2 + " in the graph"; + } else { + final var path = option.get(); + if (path.isEmpty()) { + return "No path from " + word1 + " to " + word2; + } + return graph.toDOT(r -> String.format( + "\"%s\" -> \"%s\" [label=\"%d\"%s]", + r.node1() + (path.contains(r.node1()) ? "~" : ""), + r.node2() + (path.contains(r.node2()) ? "~" : ""), + r.weight(), + (path.contains(r.node1()) && path.contains(r.node2()) + && IntStream.range(0, path.size() - 1).anyMatch( + i -> path.get(i).equals(r.node1()) && path.get(i + 1).equals(r.node2()))) + ? ",color=\"red\"" + : "")); } + } - String calcShortestPath(final String word1, final String word2) - throws IOException, InterruptedException { - final ProcessBuilder processBuilder = new ProcessBuilder("graph-easy", "--as", "ascii"); - processBuilder.redirectErrorStream(true); - Process process = null; - try { - process = processBuilder.start(); - } catch (final IOException e) { - throw e; - } - try (BufferedWriter writer = new BufferedWriter( - new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8))) { - writer.write(calcShortestPathToDOT(word1, word2)); - writer.flush(); - } catch (final IOException e) { - throw e; - } - final StringBuilder output = new StringBuilder(); - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { - String line; - while ((line = reader.readLine()) != null) { - output.append(line).append(System.lineSeparator()); - } - } catch (final IOException e) { - throw e; - } - try { - final int exitCode = process.waitFor(); - if (exitCode != 0) { - throw new IOException("Process exited with code " + exitCode); - } else { - return output.toString(); - } - } catch (final InterruptedException e) { - throw e; - } - } + Double calPageRank(final String word) { + return graph.computePageRank(word); + } - String calcShortestPathToDOT(final String word1, final String word2) { - var option = graph.findShortestPath(word1, word2); - if (option.isEmpty()) { - return "No " + word1 + " or " + word2 + " in the graph"; - } else { - final var path = option.get(); - if (path.isEmpty()) { - return "No path from " + word1 + " to " + word2; - } - return graph.toDOT(r -> String.format( - "\"%s\" -> \"%s\" [label=\"%d\"%s]", - r.node1() + (path.contains(r.node1()) ? "~" : ""), - r.node2() + (path.contains(r.node2()) ? "~" : ""), - r.weight(), - (path.contains(r.node1()) && path.contains(r.node2()) && - IntStream.range(0, path.size() - 1).anyMatch( - i -> path.get(i).equals(r.node1()) && path.get(i + 1).equals(r.node2()))) - ? ",color=\"red\"" - : "")); - } - } - - Double calPageRank(final String word) { - final var pageRank = graph.computePageRank(word); - return pageRank; - } - - String randomWalk() { - final var text = graph.randomWalk().stream().collect(Collectors.joining(" ")); - final Path path = Paths.get("output.txt"); - try { - Files.write(path, text.getBytes()); - } catch (final IOException e) { - System.err.println("Failed to write to file"); - e.printStackTrace(); - } - return text; + @SuppressFBWarnings("DM_DEFAULT_ENCODING") + String randomWalk() { + final var text = String.join(" ", graph.randomWalk()); + final Path path = Paths.get("output.txt"); + try { + Files.write(path, text.getBytes()); + } catch (final IOException e) { + System.err.println("Failed to write to file"); + e.printStackTrace(); } + return text; + } } diff --git a/flake.nix b/flake.nix index 42c1f7c..679e187 100644 --- a/flake.nix +++ b/flake.nix @@ -23,6 +23,7 @@ kotlin-language-server ktfmt jdt-language-server + checkstyle graph-easy graphviz diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..9b42019 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,94 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega