3434import java .util .concurrent .ExecutorService ;
3535import java .util .concurrent .Executors ;
3636
37- import org .slf4j .Logger ;
38- import org .slf4j .LoggerFactory ;
39-
37+ /**
38+ * Start a target app with a custom classloader. The classloader is responsible for loading
39+ * resources from:
40+ *
41+ * 1. public and config directories (probably src/main/resources too)
42+ * 2. target/classes, current app (*.class)
43+ *
44+ * The parent classloader must load 1) and all the *.jars files.
45+ *
46+ * On changes ONLY the custom classloader (App classlaoder) is bounced it.
47+ *
48+ * @author edgar
49+ *
50+ */
4051public class Hotswap {
4152
42- /** The logging system. */
43- private final Logger log = LoggerFactory .getLogger (getClass ());
44-
4553 private URLClassLoader loader ;
4654
4755 private volatile Object app ;
@@ -62,10 +70,18 @@ public class Hotswap {
6270
6371 private boolean dcevm ;
6472
73+ private List <File > dirs ;
74+
6575 public Hotswap (final String mainClass , final File [] cp ) throws IOException {
6676 this .mainClass = mainClass ;
6777 this .cp = cp ;
68- this .paths = toPath (cp );
78+ this .dirs = new ArrayList <File >();
79+ for (File file : cp ) {
80+ if (file .isDirectory ()) {
81+ dirs .add (file );
82+ }
83+ }
84+ this .paths = toPath (dirs .toArray (new File [dirs .size ()]));
6985 this .executor = Executors .newSingleThreadExecutor ();
7086 this .scanner = new Watcher (this ::onChange , paths );
7187 dcevm = System .getProperty ("java.vm.version" ).toLowerCase ().contains ("dcevm" );
@@ -124,12 +140,12 @@ private Hotswap excludes(final String excludes) {
124140 }
125141
126142 public void run () {
127- log . info ("Hotswap available on: {} " , Arrays . toString ( cp ) );
128- log . info (" unlimited runtime class redefinition: {} " , dcevm
143+ System . out . printf ("Hotswap available on: %s \n " , dirs );
144+ System . out . printf (" unlimited runtime class redefinition: %s \n " , dcevm
129145 ? "yes"
130146 : "no (see https://github.com/dcevm/dcevm)" );
131- log . info (" includes: {} " , includes );
132- log . info (" excludes: {} " , excludes );
147+ System . out . printf (" includes: %s \n " , includes );
148+ System . out . printf (" excludes: %s \n " , excludes );
133149
134150 this .scanner .start ();
135151 this .startApp ();
@@ -140,27 +156,37 @@ private void startApp() {
140156 stopApp (app );
141157 }
142158 executor .execute (() -> {
143- ClassLoader old = Thread . currentThread (). getContextClassLoader () ;
159+ URLClassLoader old = loader ;
144160 try {
145161 this .loader = newClassLoader (cp );
146- Thread .currentThread ().setContextClassLoader (loader );
147162 this .app = loader .loadClass (mainClass ).getDeclaredConstructors ()[0 ].newInstance ();
148163 app .getClass ().getMethod ("start" ).invoke (app );
149164 } catch (InvocationTargetException ex ) {
150- log .error ("Error found while starting: " + mainClass , ex .getCause ());
165+ System .err .println ("Error found while starting: " + mainClass );
166+ ex .printStackTrace ();
151167 } catch (Exception ex ) {
152- log .error ("Error found while starting: " + mainClass , ex );
168+ System .err .println ("Error found while starting: " + mainClass );
169+ ex .printStackTrace ();
153170 } finally {
154- Thread .currentThread ().setContextClassLoader (old );
171+ if (old != null ) {
172+ try {
173+ old .close ();
174+ } catch (Exception ex ) {
175+ System .err .println ("Can't close classloader" );
176+ ex .printStackTrace ();
177+ }
178+ // not sure it how useful is it, but...
179+ System .gc ();
155180 }
156- });
181+ }
182+ });
157183 }
158184
159185 private static URLClassLoader newClassLoader (final File [] cp ) throws MalformedURLException {
160186 return new URLClassLoader (toURLs (cp ), Hotswap .class .getClassLoader ()) {
161187 @ Override
162188 public String toString () {
163- return "AppLoader @" + Arrays .toString (cp );
189+ return "Hotswap @" + Arrays .toString (cp );
164190 }
165191 };
166192 }
@@ -169,21 +195,22 @@ private void onChange(final Kind<?> kind, final Path path) {
169195 try {
170196 Path candidate = relativePath (path );
171197 if (candidate == null ) {
172- log . debug ("Can't resolve path: {}... ignoring it" , path );
198+ // System. ("Can't resolve path: {}... ignoring it", path);
173199 return ;
174200 }
175201 if (!includes .matches (path )) {
176- log .debug ("ignoring file {} -> ~{}" , path , includes );
202+ // log.debug("ignoring file {} -> ~{}", path, includes);
177203 return ;
178204 }
179205 if (excludes .matches (path )) {
180- log .debug ("ignoring file {} -> {}" , path , excludes );
206+ // log.debug("ignoring file {} -> {}", path, excludes);
181207 return ;
182208 }
183209 // reload
184210 startApp ();
185211 } catch (Exception ex ) {
186- log .error ("Err found while processing: " + path , ex );
212+ System .err .printf ("Err found while processing: %s\n " + path );
213+ ex .printStackTrace ();
187214 }
188215 }
189216
@@ -201,18 +228,13 @@ private void stopApp(final Object app) {
201228 app .getClass ().getMethod ("stop" ).invoke (app );
202229 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
203230 | NoSuchMethodException | SecurityException ex ) {
204- log .error ("couldn't stop app" , ex );
205- } finally {
206- try {
207- this .loader .close ();
208- } catch (IOException ex ) {
209- log .debug ("Can't close classloader" , ex );
210- }
231+ System .err .println ("couldn't stop app" );
232+ ex .printStackTrace ();
211233 }
212234
213235 }
214236
215- private static URL [] toURLs (final File [] cp ) throws MalformedURLException {
237+ static URL [] toURLs (final File [] cp ) throws MalformedURLException {
216238 URL [] urls = new URL [cp .length ];
217239 for (int i = 0 ; i < urls .length ; i ++) {
218240 urls [i ] = cp [i ].toURI ().toURL ();
0 commit comments