Skip to content

Commit 37f7d4b

Browse files
committed
Improved watch goal
1 parent 8dfee53 commit 37f7d4b

File tree

5 files changed

+91
-94
lines changed

5 files changed

+91
-94
lines changed

src/license/THIRD-PARTY.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# Please fill the missing licenses for dependencies :
1414
#
1515
#
16-
#Mon Nov 20 23:13:24 CET 2017
16+
#Tue Nov 21 14:05:44 CET 2017
1717
classworlds--classworlds--1.1-alpha-2=
1818
commons-collections--commons-collections--3.1=
1919
dom4j--dom4j--1.6.1=

src/main/java/org/seedstack/maven/WatchMojo.java

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import org.objectweb.asm.ClassVisitor;
4141
import org.objectweb.asm.FieldVisitor;
4242
import org.objectweb.asm.MethodVisitor;
43-
import org.seedstack.maven.classloader.HotSwapURLClassLoader;
43+
import org.seedstack.maven.classloader.ReloadingClassLoader;
4444
import org.seedstack.maven.runnables.DefaultLauncherRunnable;
4545
import org.seedstack.maven.watcher.AggregatingFileChangeListener;
4646
import org.seedstack.maven.watcher.DirectoryWatcher;
@@ -57,7 +57,7 @@ public class WatchMojo extends AbstractExecutableMojo {
5757
private Thread watcherThread;
5858
private AggregatingFileChangeListener fileChangeListener;
5959
private List<String> compileSourceRoots;
60-
private HotSwapURLClassLoader hotSwapURLClassLoader;
60+
private ReloadingClassLoader reloadingClassLoader;
6161
private DefaultLauncherRunnable launcherRunnable;
6262

6363
@Override
@@ -106,26 +106,37 @@ public void run() {
106106

107107
@Override
108108
URLClassLoader getClassLoader(final URL[] classPathUrls) {
109-
hotSwapURLClassLoader = AccessController.doPrivileged(new PrivilegedAction<HotSwapURLClassLoader>() {
110-
public HotSwapURLClassLoader run() {
111-
return new HotSwapURLClassLoader(getLog(), classPathUrls);
109+
reloadingClassLoader = AccessController.doPrivileged(new PrivilegedAction<ReloadingClassLoader>() {
110+
public ReloadingClassLoader run() {
111+
return new ReloadingClassLoader(getLog(), classPathUrls);
112112
}
113113
});
114-
return hotSwapURLClassLoader;
114+
return reloadingClassLoader;
115115
}
116116

117117
private class WatchFileChangeListener extends AggregatingFileChangeListener {
118118
@Override
119119
public void onAggregatedChanges(Set<FileEvent> fileEvents) {
120-
121120
try {
121+
if (getLog().isDebugEnabled()) {
122+
for (FileEvent fileEvent : fileEvents) {
123+
getLog().debug(fileEvent.getKind().name() + ": " + fileEvent.getFile().getAbsolutePath());
124+
}
125+
}
126+
122127
Set<File> compiledFilesToRemove = new HashSet<>();
123128
Set<File> compiledFilesToUpdate = new HashSet<>();
129+
124130
analyzeEvents(fileEvents, compiledFilesToRemove, compiledFilesToUpdate);
125-
hotSwapURLClassLoader.invalidateClasses(analyzeClasses(compiledFilesToRemove, compiledFilesToUpdate));
126-
removeFiles(compiledFilesToRemove);
127-
recompile();
128-
launcherRunnable.refresh();
131+
132+
if (!compiledFilesToRemove.isEmpty() || !compiledFilesToUpdate.isEmpty()) {
133+
reloadingClassLoader.invalidateClasses(
134+
analyzeClasses(compiledFilesToRemove, compiledFilesToUpdate)
135+
);
136+
removeFiles(compiledFilesToRemove);
137+
recompile();
138+
launcherRunnable.refresh();
139+
}
129140
} catch (Exception e) {
130141
Throwable toLog = e.getCause();
131142
if (toLog == null || !toLog.getClass().getName()

src/main/java/org/seedstack/maven/classloader/DisposableClassLoader.java

Lines changed: 0 additions & 63 deletions
This file was deleted.

src/main/java/org/seedstack/maven/classloader/HotSwapURLClassLoader.java renamed to src/main/java/org/seedstack/maven/classloader/ReloadingClassLoader.java

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@
55
* License, v. 2.0. If a copy of the MPL was not distributed with this
66
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
77
*/
8-
98
package org.seedstack.maven.classloader;
109

1110
import com.fasterxml.jackson.databind.ObjectMapper;
1211
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
1312
import java.io.IOException;
1413
import java.net.URL;
1514
import java.net.URLClassLoader;
15+
import java.nio.ByteBuffer;
1616
import java.security.AccessControlContext;
1717
import java.security.AccessController;
18+
import java.security.CodeSigner;
19+
import java.security.CodeSource;
1820
import java.security.PrivilegedExceptionAction;
21+
import java.security.SecureClassLoader;
1922
import java.util.Collections;
2023
import java.util.Enumeration;
2124
import java.util.HashMap;
@@ -26,15 +29,15 @@
2629
import sun.misc.Resource;
2730
import sun.misc.URLClassPath;
2831

29-
public class HotSwapURLClassLoader extends URLClassLoader {
32+
public class ReloadingClassLoader extends URLClassLoader {
3033
private final AccessControlContext acc = AccessController.getContext();
3134
private final URLClassPath ucp;
3235
private final ObjectMapper objectMapper;
3336
private final Set<String> packagesToScan;
3437
private final Map<String, DisposableClassLoader> classLoaders = new HashMap<>();
3538
private final Log log;
3639

37-
public HotSwapURLClassLoader(Log log, URL[] urls) {
40+
public ReloadingClassLoader(Log log, URL[] urls) {
3841
super(urls);
3942
this.log = log;
4043
this.ucp = new URLClassPath(urls);
@@ -77,7 +80,7 @@ public DisposableClassLoader run() throws ClassNotFoundException {
7780
String path = name.replace('.', '/').concat(".class");
7881
Resource res = ucp.getResource(path, false);
7982
if (res != null) {
80-
return new DisposableClassLoader(HotSwapURLClassLoader.this, name, res);
83+
return new DisposableClassLoader(name, res);
8184
} else {
8285
return null;
8386
}
@@ -127,4 +130,49 @@ private Set<String> resolvePackagesToScan(URL url) {
127130
"Unable to retrieve content of application configuration file: " + url.toExternalForm(), e);
128131
}
129132
}
133+
134+
private class DisposableClassLoader extends SecureClassLoader {
135+
private final Resource res;
136+
private final String name;
137+
138+
DisposableClassLoader(String name, Resource res) {
139+
this.name = name;
140+
this.res = res;
141+
}
142+
143+
@Override
144+
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
145+
if (this.name.equals(name)) {
146+
synchronized (getClassLoadingLock(name)) {
147+
// First, check if the class has already been loaded
148+
Class<?> c = findLoadedClass(name);
149+
if (c == null) {
150+
try {
151+
ByteBuffer byteBuffer = res.getByteBuffer();
152+
if (byteBuffer != null) {
153+
// Use (direct) ByteBuffer:
154+
CodeSigner[] signers = res.getCodeSigners();
155+
CodeSource cs = new CodeSource(res.getURL(), signers);
156+
c = defineClass(name, byteBuffer, cs);
157+
} else {
158+
byte[] b = res.getBytes();
159+
// must read certificates AFTER reading bytes.
160+
CodeSigner[] signers = res.getCodeSigners();
161+
CodeSource cs = new CodeSource(res.getURL(), signers);
162+
c = defineClass(name, b, 0, b.length, cs);
163+
}
164+
} catch (IOException e) {
165+
throw new ClassNotFoundException(name, e);
166+
}
167+
if (resolve) {
168+
resolveClass(c);
169+
}
170+
}
171+
return c;
172+
}
173+
} else {
174+
return ReloadingClassLoader.this.loadClass(name, resolve);
175+
}
176+
}
177+
}
130178
}

src/main/java/org/seedstack/maven/watcher/AggregatingFileChangeListener.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,27 @@
1616

1717
public abstract class AggregatingFileChangeListener implements FileChangeListener {
1818
private final ArrayBlockingQueue<FileEvent> pending = new ArrayBlockingQueue<>(10000);
19-
private final Timer timer = new Timer();
19+
private Timer timer;
2020
private boolean stop;
2121

22-
protected AggregatingFileChangeListener() {
23-
this.timer.scheduleAtFixedRate(new TimerTask() {
24-
@Override
25-
public void run() {
26-
Set<FileEvent> aggregatedChangedFiles = new HashSet<>();
27-
pending.drainTo(aggregatedChangedFiles);
28-
if (!aggregatedChangedFiles.isEmpty()) {
29-
onAggregatedChanges(aggregatedChangedFiles);
30-
}
31-
}
32-
}, 0, 250);
33-
}
34-
3522
@Override
36-
public void onChange(Set<FileEvent> fileEvents) {
23+
public void onChange(final Set<FileEvent> fileEvents) {
3724
if (!stop) {
25+
if (this.timer != null) {
26+
this.timer.cancel();
27+
}
28+
3829
pending.addAll(fileEvents);
30+
31+
this.timer = new Timer();
32+
this.timer.schedule(new TimerTask() {
33+
@Override
34+
public void run() {
35+
HashSet<FileEvent> aggregatedFileEvents = new HashSet<>();
36+
pending.drainTo(aggregatedFileEvents);
37+
onAggregatedChanges(aggregatedFileEvents);
38+
}
39+
}, 500);
3940
}
4041
}
4142

0 commit comments

Comments
 (0)