summaryrefslogtreecommitdiff
path: root/src/main/java/org/javacs/SourcePath.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/javacs/SourcePath.java')
-rw-r--r--src/main/java/org/javacs/SourcePath.java146
1 files changed, 146 insertions, 0 deletions
diff --git a/src/main/java/org/javacs/SourcePath.java b/src/main/java/org/javacs/SourcePath.java
new file mode 100644
index 0000000..75c4d4c
--- /dev/null
+++ b/src/main/java/org/javacs/SourcePath.java
@@ -0,0 +1,146 @@
+package org.javacs;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.logging.Logger;
+
+class SourcePath {
+ private final Set<Path> workspaceRoots;
+ /** All .java files under workspaceRoot */
+ private final Set<Path> allJavaFiles;
+ /** Source roots, computed from allJavaFiles by looking at package declarations. */
+ private Set<Path> sourceRoots = new HashSet<>();
+
+ public SourcePath(Set<Path> workspaceRoots) {
+ this.workspaceRoots = Collections.unmodifiableSet(workspaceRoots);
+ this.sourceRoots = sourcePath(workspaceRoots);
+ this.allJavaFiles = allJavaFilesInDirs(sourceRoots);
+ }
+
+ /**
+ * A .java file has been created. Update allJavaFiles, but don't recalculate sourceRoots until the file is saved,
+ * because the user hasn't had a change to type in a package name yet.
+ */
+ boolean create(Set<Path> javaFiles) {
+ if (javaFiles.isEmpty()) return false;
+ allJavaFiles.addAll(javaFiles);
+ return checkSourceRoots();
+ }
+
+ /**
+ * A .java file has been deleted. If the file is the last one in a particular source root, we should remove that
+ * source root.
+ */
+ boolean delete(Set<Path> javaFiles) {
+ if (javaFiles.isEmpty()) return false;
+ allJavaFiles.removeAll(javaFiles);
+ return checkSourceRoots();
+ }
+
+ /**
+ * A .java file has been edited and saved. If the package declaration has been changed, we may need to recalculate
+ * sourceRoots.
+ */
+ boolean update(Path javaFile) {
+ for (var root : sourceRoots) {
+ if (!javaFile.startsWith(root)) continue;
+ var dir = javaFile.getParent();
+ var expectedPackageName = root.relativize(dir).toString().replace('/', '.');
+ var parse = Parser.parse(javaFile);
+ var foundPackageName = Objects.toString(parse.getPackageName(), "");
+ if (!expectedPackageName.equals(foundPackageName)) {
+ LOG.info(
+ String.format(
+ "%s is in %s, which implies package %s, but now has package %s",
+ javaFile.getFileName(), root, expectedPackageName, foundPackageName));
+ return checkSourceRoots();
+ }
+ }
+ return false;
+ }
+
+ Set<Path> sourceRoots() {
+ return Collections.unmodifiableSet(sourceRoots);
+ }
+
+ Set<Path> allJavaFiles() {
+ return Collections.unmodifiableSet(allJavaFiles);
+ }
+
+ private boolean checkSourceRoots() {
+ var newSourceRoots = sourcePath(workspaceRoots);
+ if (!newSourceRoots.equals(sourceRoots)) {
+ LOG.info(String.format("Source path has changed from %s to %s", sourceRoots, newSourceRoots));
+ sourceRoots = newSourceRoots;
+ return true;
+ }
+ return false;
+ }
+
+ private static Set<Path> allJavaFilesInDirs(Set<Path> dirs) {
+ var all = new HashSet<Path>();
+ var match = FileSystems.getDefault().getPathMatcher("glob:*.java");
+ for (var dir : dirs) {
+ try {
+ Files.walk(dir).filter(java -> match.matches(java.getFileName())).forEach(all::add);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return all;
+ }
+
+ private static Set<Path> sourcePath(Set<Path> workspaceRoots) {
+ LOG.info("Searching for source roots in " + workspaceRoots);
+
+ var certaintyThreshold = 10;
+ var sourceRoots = new HashMap<Path, Integer>();
+ fileLoop:
+ for (var file : allJavaFilesInDirs(workspaceRoots)) {
+ // First, check if we already have a high-confidence source root containing file
+ for (var root : sourceRoots.keySet()) {
+ var confidence = sourceRoots.get(root);
+ if (file.startsWith(root) && confidence > certaintyThreshold) {
+ continue fileLoop;
+ }
+ }
+ // Otherwise, parse the file and look at its package declaration
+ var parse = Parser.parse(file);
+ var packageName = Objects.toString(parse.getPackageName(), "");
+ var packagePath = packageName.replace('.', File.separatorChar);
+ // If file has no package declaration, don't try to guess a source root
+ // This is probably a new file that the user will add a package declaration to later
+ if (packagePath.isEmpty()) {
+ LOG.warning("Ignoring file with missing package declaration " + file);
+ continue fileLoop;
+ }
+ // If path to file contradicts package declaration, give up
+ var dir = file.getParent();
+ if (!dir.endsWith(packagePath)) {
+ LOG.warning("Java source file " + file + " is not in " + packagePath);
+ continue fileLoop;
+ }
+ // Otherwise, use the package declaration to guess the source root
+ var up = Paths.get(packagePath).getNameCount();
+ var sourceRoot = dir;
+ for (int i = 0; i < up; i++) {
+ sourceRoot = sourceRoot.getParent();
+ }
+ // Increment our confidence in sourceRoot as a source root
+ var count = sourceRoots.getOrDefault(sourceRoot, 0);
+ sourceRoots.put(sourceRoot, count + 1);
+ }
+ return sourceRoots.keySet();
+ }
+
+ private static final Logger LOG = Logger.getLogger("main");
+}