Skip to content

Commit

Permalink
Next attempt
Browse files Browse the repository at this point in the history
  • Loading branch information
mkouba committed Oct 23, 2023
1 parent fa3ce88 commit d33c392
Showing 1 changed file with 92 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -901,14 +901,13 @@ Set<String> checkForFileChange(Function<DevModeContext.ModuleInfo, DevModeContex
final Set<Path> seen = new HashSet<>(moduleResources);
try {
for (Path root : roots) {
//since the stream is Closeable, use a try with resources so the underlying iterator is closed
try (final Stream<Path> walk = Files.walk(root)) {
walk.forEach(path -> {
try {
Path relative = root.relativize(path);
Path target = outputDir.resolve(relative);
seen.remove(target);
if (!timestampSet.watchedFileTimestamps.containsKey(path)) {
if (!timestampSet.watchedPaths.containsKey(path)) {
moduleResources.add(target);
if (!Files.exists(target) || Files.getLastModifiedTime(target).toMillis() < Files
.getLastModifiedTime(path).toMillis()) {
Expand Down Expand Up @@ -944,70 +943,67 @@ Set<String> checkForFileChange(Function<DevModeContext.ModuleInfo, DevModeContex
}
}

for (Path watchedFilePath : timestampSet.watchedFileTimestamps.keySet()) {
boolean isAbsolute = watchedFilePath.isAbsolute();
List<Path> watchedRoots = roots;
for (WatchedPath watchedPath : timestampSet.watchedPaths.values()) {
boolean isAbsolute = watchedPath.matchPath.isAbsolute();
List<Path> watchedRoots;
if (isAbsolute) {
// absolute files are assumed to be read directly from the project root.
// They therefore do not get copied to, and deleted from, the outputdir.
watchedRoots = List.of(Path.of("/"));
} else {
watchedRoots = roots;
}
if (watchedRoots.isEmpty()) {
// this compilation unit has no resource roots, and therefore can not have this file
continue;
}
boolean pathCurrentlyExisting = false;
boolean pathPreviouslyExisting = false;
for (Path root : watchedRoots) {
Path file = root.resolve(watchedFilePath);
if (Files.exists(file)) {
pathCurrentlyExisting = true;
try {
long value = Files.getLastModifiedTime(file).toMillis();
Long existing = timestampSet.watchedFileTimestamps.get(file);
//existing can be null when running tests
//as there is both normal and test resources, but only one set of watched timestampts
if (existing != null && value > existing) {
ret.add(file.toString());
//a write can be a 'truncate' + 'write'
//if the file is empty we may be seeing the middle of a write
if (Files.size(file) == 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
//ignore
}
if (Files.exists(watchedPath.filePath)) {
pathCurrentlyExisting = true;
try {
long current = Files.getLastModifiedTime(watchedPath.filePath).toMillis();
long last = watchedPath.lastModified;
if (current > last) {
ret.add(watchedPath.matchPath.toString());
//a write can be a 'truncate' + 'write'
//if the file is empty we may be seeing the middle of a write
if (Files.size(watchedPath.filePath) == 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
//ignore
}
//re-read, as we may have read the original TS if the middle of
//a truncate+write, even if the write had completed by the time
//we read the size
value = Files.getLastModifiedTime(file).toMillis();

log.infof("File change detected: %s", file);
if (!isAbsolute && doCopy && !Files.isDirectory(file)) {
Path target = outputDir.resolve(watchedFilePath);
byte[] data = Files.readAllBytes(file);
try (FileOutputStream out = new FileOutputStream(target.toFile())) {
out.write(data);
}
}
//re-read, as we may have read the original TS if the middle of
//a truncate+write, even if the write had completed by the time
//we read the size
current = Files.getLastModifiedTime(watchedPath.filePath).toMillis();

log.infof("File change detected: %s", watchedPath.filePath);
if (!isAbsolute && doCopy && !Files.isDirectory(watchedPath.filePath)) {
Path target = outputDir.resolve(watchedPath.matchPath);
byte[] data = Files.readAllBytes(watchedPath.filePath);
try (FileOutputStream out = new FileOutputStream(target.toFile())) {
out.write(data);
}
timestampSet.watchedFileTimestamps.put(file, value);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
watchedPath.lastModified = current;
}
} else {
Long prevValue = timestampSet.watchedFileTimestamps.put(file, 0L);
pathPreviouslyExisting = pathPreviouslyExisting || (prevValue != null && prevValue > 0);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} else {
long prevValue = watchedPath.lastModified;
watchedPath.lastModified = 0L;
pathPreviouslyExisting = prevValue > 0;
}
if (!pathCurrentlyExisting) {
if (pathPreviouslyExisting) {
ret.add(watchedFilePath.toString());
ret.add(watchedPath.matchPath.toString());
}

if (!isAbsolute) {
Path target = outputDir.resolve(watchedFilePath);
Path target = outputDir.resolve(watchedPath.matchPath);
try {
FileUtil.deleteIfExists(target);
} catch (IOException e) {
Expand Down Expand Up @@ -1087,8 +1083,6 @@ public RuntimeUpdatesProcessor setWatchedFilePaths(Map<String, Boolean> watchedF
s -> s.getTest().isPresent() ? asList(s.getTest().get(), s.getMain()) : singletonList(s.getMain()),
watchedFilePredicates);
} else {
main.watchedFileTimestamps.clear();
main.watchedFilePredicates = watchedFilePredicates;
setWatchedFilePathsInternal(watchedFilePaths, main, s -> singletonList(s.getMain()), watchedFilePredicates);
}
return this;
Expand All @@ -1097,8 +1091,10 @@ public RuntimeUpdatesProcessor setWatchedFilePaths(Map<String, Boolean> watchedF
private RuntimeUpdatesProcessor setWatchedFilePathsInternal(Map<String, Boolean> watchedFilePaths,
TimestampSet timestamps, Function<DevModeContext.ModuleInfo, List<DevModeContext.CompilationUnit>> cuf,
List<Entry<Predicate<String>, Boolean>> watchedFilePredicates) {

timestamps.watchedFilePaths = watchedFilePaths;
Map<String, Boolean> extraWatchedFilePaths = new HashMap<>();
timestamps.watchedFilePredicates = watchedFilePredicates;

Set<String> watchedRootPaths = new HashSet<>();
for (DevModeContext.ModuleInfo module : context.getAllModules()) {
List<DevModeContext.CompilationUnit> compilationUnits = cuf.apply(module);
Expand All @@ -1112,7 +1108,7 @@ private RuntimeUpdatesProcessor setWatchedFilePathsInternal(Map<String, Boolean>
rootPaths = PathList.of(Path.of(rootPath));
}
for (Path root : rootPaths) {
// First find all matching paths from the root
// First find all matching paths from all roots
try (final Stream<Path> walk = Files.walk(root)) {
walk.forEach(path -> {
if (path.equals(root)) {
Expand All @@ -1122,11 +1118,15 @@ private RuntimeUpdatesProcessor setWatchedFilePathsInternal(Map<String, Boolean>
// /some/more/complex/path/src/main/resources/foo/bar.txt -> foo/bar.txt
Path relativePath = root.relativize(path);
String relativePathStr = relativePath.toString();
if (watchedFilePaths.containsKey(relativePathStr)
|| watchedFilePredicates.stream().anyMatch(e -> e.getKey().test(relativePathStr))) {
Boolean restart = watchedFilePaths.get(relativePathStr);
if (restart == null) {
restart = watchedFilePredicates.stream().filter(p -> p.getKey().test(relativePathStr))
.map(Entry::getValue).findFirst().orElse(null);
}
if (restart != null) {
log.debugf("Watch %s from: %s", relativePathStr, root);
watchedRootPaths.add(relativePathStr);
putLastModifiedTime(path, relativePath, timestamps);
putLastModifiedTime(path, relativePath, restart, timestamps);
}
});
} catch (IOException e) {
Expand All @@ -1135,42 +1135,35 @@ private RuntimeUpdatesProcessor setWatchedFilePathsInternal(Map<String, Boolean>

// Then process watched paths that are not matched with a path from a resource root,
// for example absolute paths and glob patterns
for (String watchedFilePath : watchedFilePaths.keySet()) {
for (Entry<String, Boolean> e : watchedFilePaths.entrySet()) {
String watchedFilePath = e.getKey();
Path path = Paths.get(watchedFilePath);
if (path.isAbsolute()) {
if (Files.exists(path)) {
log.debugf("Watch %s", path);
putLastModifiedTime(path, path, timestamps);
putLastModifiedTime(path, path, e.getValue(), timestamps);
}
} else if (!watchedRootPaths.contains(watchedFilePath) && maybeGlobPattern(watchedFilePath)) {
} else if (!watchedRootPaths.contains(e.getKey()) && maybeGlobPattern(watchedFilePath)) {
Path resolvedPath = root.resolve(watchedFilePath);
Map<Path, Long> extraWatchedFileTimestamps = expandGlobPattern(root, resolvedPath, watchedFilePath);
if (!extraWatchedFileTimestamps.isEmpty()) {
timestamps.watchedFileTimestamps.put(resolvedPath, 0L);
timestamps.watchedFileTimestamps.putAll(extraWatchedFileTimestamps);
for (Path extraPath : extraWatchedFileTimestamps.keySet()) {
extraWatchedFilePaths.put(extraPath.toString(),
timestamps.watchedFilePaths.get(watchedFilePath));
}
timestamps.watchedFileTimestamps.putAll(extraWatchedFileTimestamps);
for (WatchedPath extra : expandGlobPattern(root, resolvedPath, watchedFilePath, e.getValue())) {
timestamps.watchedPaths.put(extra.filePath, extra);
}
}
}
}
}
}
timestamps.watchedFilePaths.putAll(extraWatchedFilePaths);
return this;
}

private boolean maybeGlobPattern(String path) {
return path.contains("*") || path.contains("?");
}

private void putLastModifiedTime(Path filePath, Path keyPath, TimestampSet timestamps) {
private void putLastModifiedTime(Path path, Path relativePath, boolean restart, TimestampSet timestamps) {
try {
FileTime lastModifiedTime = Files.getLastModifiedTime(filePath);
timestamps.watchedFileTimestamps.put(keyPath, lastModifiedTime.toMillis());
FileTime lastModifiedTime = Files.getLastModifiedTime(path);
timestamps.watchedPaths.put(path, new WatchedPath(path, relativePath, restart, lastModifiedTime.toMillis()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Expand Down Expand Up @@ -1210,17 +1203,17 @@ public void close() throws IOException {
}
}

private Map<Path, Long> expandGlobPattern(Path root, Path path, String pattern) {
private List<WatchedPath> expandGlobPattern(Path root, Path path, String pattern, boolean restart) {
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + path.toString());
Map<Path, Long> files = new HashMap<>();
List<WatchedPath> files = new ArrayList<>();
try {
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (pathMatcher.matches(file)) {
Path relativePath = root.relativize(file);
log.debugf("Glob pattern [%s] matched %s from %s", pattern, relativePath, root);
files.put(relativePath, attrs.lastModifiedTime().toMillis());
files.add(new WatchedPath(file, relativePath, restart, attrs.lastModifiedTime().toMillis()));
}
return FileVisitResult.CONTINUE;
}
Expand Down Expand Up @@ -1261,21 +1254,21 @@ public boolean isLiveReloadEnabled() {
}

static class TimestampSet {
final Map<Path, Long> watchedFileTimestamps = new ConcurrentHashMap<>();
final Map<Path, Long> classFileChangeTimeStamps = new ConcurrentHashMap<>();
final Map<Path, Path> classFilePathToSourceFilePath = new ConcurrentHashMap<>();
// file path -> isRestartNeeded
private volatile Map<String, Boolean> watchedFilePaths = Collections.emptyMap();
volatile List<Entry<Predicate<String>, Boolean>> watchedFilePredicates = Collections.emptyList();
volatile Map<Path, WatchedPath> watchedPaths = new ConcurrentHashMap<>();

// The current paths and predicates from all HotDeploymentWatchedFileBuildItems
volatile Map<String, Boolean> watchedFilePaths;
volatile List<Entry<Predicate<String>, Boolean>> watchedFilePredicates;

public void merge(TimestampSet other) {
watchedFileTimestamps.putAll(other.watchedFileTimestamps);
classFileChangeTimeStamps.putAll(other.classFileChangeTimeStamps);
classFilePathToSourceFilePath.putAll(other.classFilePathToSourceFilePath);
Map<String, Boolean> newVal = new HashMap<>(watchedFilePaths);
newVal.putAll(other.watchedFilePaths);
watchedFilePaths = newVal;
// The list of predicates should be effectively immutable
Map<Path, WatchedPath> newVal = new HashMap<>(watchedPaths);
newVal.putAll(other.watchedPaths);
watchedPaths = newVal;
watchedFilePaths = other.watchedFilePaths;
watchedFilePredicates = other.watchedFilePredicates;
}

Expand All @@ -1291,6 +1284,25 @@ boolean isWatchedFileRestartNeeded(String changedFile) {
}
return ret;
}

}

static class WatchedPath {

final Path filePath;
// Used to match a HotDeploymentWatchedFileBuildItem
final Path matchPath;
// Last modification time
volatile long lastModified;
// HotDeploymentWatchedFileBuildItem.restartNeeded
final boolean restartNeeded;

private WatchedPath(Path path, Path relativePath, boolean restartNeeded, long lastModified) {
this.filePath = path;
this.matchPath = relativePath;
this.restartNeeded = restartNeeded;
}

}

public String[] getCommandLineArgs() {
Expand Down

0 comments on commit d33c392

Please sign in to comment.