2222import java .io .FileInputStream ;
2323import java .io .IOException ;
2424import java .io .InputStream ;
25+ import java .io .PrintWriter ;
26+ import java .io .StringWriter ;
2527import java .lang .reflect .InvocationTargetException ;
2628import java .lang .reflect .Method ;
2729import java .nio .file .FileSystems ;
2830import java .nio .file .Path ;
2931import java .nio .file .PathMatcher ;
3032import java .nio .file .WatchEvent .Kind ;
33+ import java .time .LocalTime ;
34+ import java .time .format .DateTimeFormatter ;
3135import java .util .ArrayList ;
32- import java .util .Arrays ;
3336import java .util .List ;
3437import java .util .Map .Entry ;
3538import java .util .Properties ;
3639import java .util .concurrent .ExecutorService ;
3740import java .util .concurrent .Executors ;
41+ import java .util .concurrent .atomic .AtomicReference ;
3842
3943import org .jboss .modules .Module ;
4044import org .jboss .modules .ModuleClassLoader ;
4145import org .jboss .modules .ModuleIdentifier ;
42- import org .jboss .modules .log .StreamModuleLogger ;
46+ import org .jboss .modules .ModuleLoader ;
47+ import org .jboss .modules .log .ModuleLogger ;
4348
4449public class AppModule {
4550
46- public static boolean DEBUG ;
51+ private static boolean DEBUG ;
4752
48- public static boolean TRACE ;
53+ private static boolean TRACE ;
54+
55+ static {
56+ logLevel ();
57+ }
4958
5059 private AppModuleLoader loader ;
51- private Path [] paths = { new File (System .getProperty ("user.dir" )). toPath () } ;
60+ private File basedir = new File (System .getProperty ("user.dir" ));
5261 private ExecutorService executor ;
5362 private Watcher scanner ;
5463 private PathMatcher includes ;
5564 private PathMatcher excludes ;
5665 private volatile Object app ;
66+ private AtomicReference <String > hash = new AtomicReference <String >("" );
5767 private ModuleIdentifier mId ;
5868 private String mainClass ;
5969 private volatile Module module ;
@@ -64,7 +74,9 @@ public AppModule(final String mId, final String mainClass, final File... cp)
6474 loader = AppModuleLoader .build (mId , mainClass , cp );
6575 this .mId = ModuleIdentifier .create (mId );
6676 this .executor = Executors .newSingleThreadExecutor (task -> new Thread (task , "HotSwap" ));
67- this .scanner = new Watcher (this ::onChange , paths );
77+ this .scanner = new Watcher (this ::onChange , new Path []{basedir .toPath () });
78+ includes ("**/*.class,**/*.conf,**/*.properties,*.js, src/*.js" );
79+ excludes ("" );
6880 }
6981
7082 public static void main (final String [] args ) throws Exception {
@@ -98,17 +110,12 @@ public static void main(final String[] args) throws Exception {
98110 }
99111 }
100112 // set log level, once we call setSystemProps
101- DEBUG = Integer .getInteger ("logLevel" , 2 ) == 1 ;
102- TRACE = Integer .getInteger ("logLevel" , 2 ) == 0 ;
113+ logLevel ();
103114
104115 if (cp .isEmpty ()) {
105116 cp .add (new File (System .getProperty ("user.dir" )));
106117 }
107118
108- if (TRACE ) {
109- Module .setModuleLogger (new StreamModuleLogger (System .out ));
110- }
111-
112119 AppModule launcher = new AppModule (args [0 ], args [1 ], cp .toArray (new File [cp .size ()]))
113120 .includes (includes )
114121 .excludes (excludes );
@@ -132,9 +139,9 @@ private static void setSystemProperties(final File sysprops) throws IOException
132139 }
133140
134141 public void run () {
135- System . out . printf ("Hotswap available on: %s%n " , Arrays . toString ( paths ));
136- System . out . printf (" includes: %s%n " , includes );
137- System . out . printf (" excludes: %s%n " , excludes );
142+ info ("Hotswap available on: %s" , basedir . getPath ( ));
143+ info (" includes: %s" , includes );
144+ info (" excludes: %s" , excludes );
138145
139146 this .scanner .start ();
140147 this .startApp ();
@@ -144,6 +151,7 @@ private void startApp() {
144151 if (app != null ) {
145152 stopApp (app );
146153 }
154+ debug ("scheduling: %s" , mainClass );
147155 executor .execute (() -> {
148156 ClassLoader ctxLoader = Thread .currentThread ().getContextClassLoader ();
149157 try {
@@ -162,14 +170,14 @@ private void startApp() {
162170 this .app = mcloader .loadClass (mainClass )
163171 .getDeclaredConstructors ()[0 ].newInstance ();
164172 }
173+ debug ("starting: %s" , mainClass );
165174 app .getClass ().getMethod ("start" ).invoke (app );
166175 } catch (Throwable ex ) {
167- System .err .println (mainClass + ".start() resulted in error" );
168176 Throwable cause = ex ;
169177 if (ex instanceof InvocationTargetException ) {
170178 cause = ((InvocationTargetException ) ex ).getTargetException ();
171179 }
172- cause . printStackTrace ( );
180+ error ( "%s.start() resulted in error" , mainClass , cause );
173181 } finally {
174182 Thread .currentThread ().setContextClassLoader (ctxLoader );
175183 }
@@ -178,12 +186,13 @@ private void startApp() {
178186
179187 private void stopApp (final Object app ) {
180188 try {
189+ debug ("stopping: %s" , mainClass );
181190 app .getClass ().getMethod ("stop" ).invoke (app );
182191 } catch (Throwable ex ) {
183- System .err .println (app .getClass ().getName () + ".stop() resulted in error" );
184- ex .printStackTrace ();
192+ error ("%s.stop() resulted in error" , mainClass , ex );
185193 } finally {
186194 try {
195+ debug ("unloading: %s" , mainClass );
187196 loader .unload (module );
188197 } catch (Throwable ex ) {
189198 // sshhhh
@@ -192,34 +201,43 @@ private void stopApp(final Object app) {
192201
193202 }
194203
195- private AppModule includes (final String includes ) {
204+ public AppModule includes (final String includes ) {
196205 this .includes = pathMatcher (includes );
197206 return this ;
198207 }
199208
200- private AppModule excludes (final String excludes ) {
209+ public AppModule excludes (final String excludes ) {
201210 this .excludes = pathMatcher (excludes );
202211 return this ;
203212 }
204213
205214 private void onChange (final Kind <?> kind , final Path path ) {
206215 try {
216+ debug ("OnChange: %s(%s)" , path , kind );
207217 Path candidate = relativePath (path );
208218 if (candidate == null || !includes .matches (candidate ) || excludes .matches (candidate )) {
219+ debug ("Ignoring change: %s" , path );
209220 return ;
210221 }
211- // reload
212- startApp ();
222+ // weak hash check: avoid change on conf/* that are propagated to target/classs by maven.
223+ File f = candidate .toFile ();
224+ String h = f .getName () + ":" + f .length ();
225+ if (!hash .getAndSet (h ).equals (h )) {
226+ debug ("File change detected: %s" , path );
227+ // reload
228+ startApp ();
229+ } else {
230+ debug ("Ignoring change: %s" , path );
231+ }
213232 } catch (Exception ex ) {
214233 ex .printStackTrace ();
215234 }
216235 }
217236
218237 private Path relativePath (final Path path ) {
219- for (Path root : paths ) {
220- if (path .startsWith (root )) {
221- return root .relativize (path );
222- }
238+ Path root = basedir .toPath ();
239+ if (path .startsWith (root )) {
240+ return root .relativize (path );
223241 }
224242 return null ;
225243 }
@@ -248,4 +266,138 @@ public String toString() {
248266 };
249267 }
250268
269+ private static void logLevel () {
270+ DEBUG = "debug" .equalsIgnoreCase (System .getProperty ("logLevel" , "" ));
271+
272+ TRACE = "trace" .equalsIgnoreCase (System .getProperty ("logLevel" , "" ));
273+
274+ if (TRACE ) {
275+ Module .setModuleLogger (new ModuleLogger () {
276+
277+ @ Override
278+ public void trace (final Throwable t , final String format , final Object arg1 ,
279+ final Object arg2 , final Object arg3 ) {
280+ AppModule .trace (format , arg1 , arg2 , arg3 , t );
281+ }
282+
283+ @ Override
284+ public void trace (final Throwable t , final String format , final Object arg1 ,
285+ final Object arg2 ) {
286+ AppModule .trace (format , arg1 , arg2 , t );
287+ }
288+
289+ @ Override
290+ public void trace (final String format , final Object arg1 , final Object arg2 ,
291+ final Object arg3 ) {
292+ AppModule .trace (format , arg1 , arg2 , arg3 );
293+ }
294+
295+ @ Override
296+ public void trace (final Throwable t , final String format , final Object ... args ) {
297+ Object [] values = new Object [args .length + 1 ];
298+ System .arraycopy (args , 0 , values , 0 , args .length );
299+ values [values .length - 1 ] = t ;
300+ AppModule .trace (format , values );
301+ }
302+
303+ @ Override
304+ public void trace (final Throwable t , final String format , final Object arg1 ) {
305+ AppModule .trace (format , arg1 , t );
306+ }
307+
308+ @ Override
309+ public void trace (final String format , final Object arg1 , final Object arg2 ) {
310+ AppModule .trace (format , arg1 , arg2 );
311+
312+ }
313+
314+ @ Override
315+ public void trace (final Throwable t , final String message ) {
316+ AppModule .trace (message , t );
317+ }
318+
319+ @ Override
320+ public void trace (final String format , final Object ... args ) {
321+ AppModule .trace (format , args );
322+ }
323+
324+ @ Override
325+ public void trace (final String format , final Object arg1 ) {
326+ AppModule .trace (format , arg1 );
327+ }
328+
329+ @ Override
330+ public void trace (final String message ) {
331+ AppModule .trace (message );
332+ }
333+
334+ @ Override
335+ public void providerUnloadable (final String name , final ClassLoader loader ) {
336+ }
337+
338+ @ Override
339+ public void moduleDefined (final ModuleIdentifier identifier ,
340+ final ModuleLoader moduleLoader ) {
341+ }
342+
343+ @ Override
344+ public void greeting () {
345+ }
346+
347+ @ Override
348+ public void classDefined (final String name , final Module module ) {
349+ }
350+
351+ @ Override
352+ public void classDefineFailed (final Throwable throwable , final String className ,
353+ final Module module ) {
354+ }
355+ });
356+ }
357+ }
358+
359+ public static void info (final String message , final Object ... args ) {
360+ System .out .println (format (message , args ));
361+ }
362+
363+ public static void error (final String message , final Object ... args ) {
364+ System .err .println (format (message , args ));
365+ }
366+
367+ public static void debug (final String message , final Object ... args ) {
368+ if (DEBUG ) {
369+ System .out .println (format (message , args ));
370+ }
371+ }
372+
373+ public static void trace (final String message , final Object ... args ) {
374+ if (TRACE ) {
375+ System .out .println (format (message , args ));
376+ }
377+ }
378+
379+ private static String format (final String message , final Object ... args ) {
380+ Object [] values = args ;
381+ Throwable x = null ;
382+ if (args .length > 0 ) {
383+ if (args [args .length - 1 ] instanceof Throwable ) {
384+ x = (Throwable ) args [args .length - 1 ];
385+ values = new Object [args .length - 1 ];
386+ System .arraycopy (args , 0 , values , 0 , values .length );
387+ }
388+ }
389+ String msg = String .format (message , values );
390+ StringBuilder buff = new StringBuilder ();
391+ buff .append ("[HotSwap|" )
392+ .append (Thread .currentThread ().getName ()).append ("|" )
393+ .append (LocalTime .now ().format (DateTimeFormatter .ofPattern ("hh:mm:ss" )))
394+ .append ("]: " ).append (msg );
395+ if (x != null ) {
396+ buff .append ("\n " );
397+ StringWriter writer = new StringWriter ();
398+ x .printStackTrace (new PrintWriter (writer ));
399+ buff .append (writer );
400+ }
401+ return buff .toString ();
402+ }
251403}
0 commit comments