├── .gitignore ├── LICENSE ├── README.md ├── classreloading.iml ├── data └── example5 │ ├── config.properties │ └── web │ ├── angular_1.3.1 │ ├── angular-animate.min.js │ ├── angular-resource.min.js │ ├── angular-sanitize.min.js │ ├── angular-ui-router.min.js │ ├── angular.js │ └── angular.min.js │ ├── bootstrap_3.2.0 │ ├── bootstrap.min.css │ ├── bootstrap.min.js │ └── ui-bootstrap-tpls-0.11.2.min.js │ ├── favicon.ico │ ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff │ ├── jquery-2.1.1 │ └── jquery-2.1.1.min.js │ └── spa │ ├── app.js │ ├── hello │ ├── AddContactModal.jade │ ├── Hello.jade │ └── Hello.js │ └── spa.jade ├── pom.xml ├── push_github.cmd ├── run_example1.bat ├── run_example2.bat ├── run_example3.bat ├── run_example4.bat ├── run_example5.bat └── src └── main ├── ai ├── 1_2_loaders.ai ├── 2_many_loaders.ai ├── 3_context_reloading.ai ├── 4_persisting_connection.ai ├── 5_reloading_web_context.ai └── 5_web_app.ai ├── java └── qj │ ├── blog │ └── classreloading │ │ ├── example1 │ │ └── StaticInt.java │ │ ├── example2 │ │ └── ReloadingContinuously.java │ │ ├── example3 │ │ └── ContextReloading.java │ │ ├── example4 │ │ ├── KeepConnectionPool.java │ │ ├── crossing │ │ │ ├── Connection.java │ │ │ └── ConnectionPool.java │ │ └── reloadable │ │ │ ├── Context.java │ │ │ └── UserService.java │ │ └── example5 │ │ ├── LittlePhoneBookMain.java │ │ └── reloadable │ │ ├── Context.java │ │ ├── dao │ │ └── ContactDAO.java │ │ ├── model │ │ └── Contact.java │ │ └── servlet │ │ ├── ContactServlet.java │ │ └── JadeServlet.java │ ├── tool │ ├── sql │ │ ├── Builder.java │ │ ├── SQLUtil.java │ │ └── Template.java │ └── web │ │ ├── ReloadingContext.java │ │ ├── ReloadingWebContext.java │ │ └── ResourceFilter.java │ └── util │ ├── Cols.java │ ├── FileUtil.java │ ├── IOUtil.java │ ├── NameCaseUtil.java │ ├── ObjectUtil.java │ ├── PropertiesUtil.java │ ├── ReflectUtil.java │ ├── RegexUtil.java │ ├── StringChange.java │ ├── StringUtil.java │ ├── SystemUtil.java │ ├── ThreadUtil.java │ ├── funct │ ├── F0.java │ ├── F1.java │ ├── F2.java │ ├── Fs.java │ ├── FsGenerated.java │ ├── P0.java │ ├── P1.java │ ├── P2.java │ └── P3.java │ ├── lang │ ├── AggressiveClassLoader.java │ ├── DynamicClassLoader.java │ └── ExceptingClassLoader.java │ └── math │ └── Range.java └── kramdown ├── v1.kd ├── v2.kd ├── v3 ├── content.md └── outline.md └── v4 ├── content.md └── v4-NM1.txt /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2016 Quan Le. http://quanla.github.io/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### If you are using Eclipse: 3 | - Run command: "mvn eclipse:eclipse" to generate Eclipse's project files 4 | - Set your output path to "target/classes" 5 | 6 | ### If you are using IntelliJ: 7 | 8 | - Set your output path to "target/classes" 9 | - Import the project's pom file 10 | - Intellij will not auto-compile when you are running any example, so you have to choose either: 11 | - Run the examples inside IntelliJ then you have to compile the class manually with (Alt+B E) 12 | - Run the examples outside IntelliJ with the run_example*.bat 13 | 14 | 下文介绍比较详细 15 | https://www.toptal.com/java/java-wizardry-101-a-guide-to-java-class-reloading 16 | -------------------------------------------------------------------------------- /classreloading.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /data/example5/config.properties: -------------------------------------------------------------------------------- 1 | web.port=1080 2 | 3 | db.driver=org.sqlite.JDBC 4 | 5 | db.url=jdbc:sqlite::memory: 6 | 7 | 8 | -------------------------------------------------------------------------------- /data/example5/web/angular_1.3.1/angular-animate.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.1 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(M,f,S){'use strict';f.module("ngAnimate",["ng"]).directive("ngAnimateChildren",function(){return function(T,B,k){k=k.ngAnimateChildren;f.isString(k)&&0===k.length?B.data("$$ngAnimateChildren",!0):T.$watch(k,function(f){B.data("$$ngAnimateChildren",!!f)})}}).factory("$$animateReflow",["$$rAF","$document",function(f,B){return function(k){return f(function(){k()})}}]).config(["$provide","$animateProvider",function(T,B){function k(f){for(var g=0;g=A&&d>=x&&c()}var m=k(d);a=d.data("$$ngAnimateCSS3Data");if(-1!=m.getAttribute("class").indexOf(b)&&a){var q="",r="";g(b.split(" "),function(a,d){var b=(0=c;e--)d.end&&d.end(f[e]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,m,f=[],n=a,h;for(f.last=function(){return f[f.length-1]};a;){h="";m=!0;if(f.last()&&y[f.last()])a=a.replace(new RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(I,"$1").replace(J,"$1");d.chars&&d.chars(s(b));return""}),e("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(d.comment&&d.comment(a.substring(4, 8 | b)),a=a.substring(b+3),m=!1);else if(z.test(a)){if(b=a.match(z))a=a.replace(b[0],""),m=!1}else if(K.test(a)){if(b=a.match(A))a=a.substring(b[0].length),b[0].replace(A,e),m=!1}else L.test(a)&&((b=a.match(B))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(B,c)),m=!1):(h+="<",a=a.substring(1)));m&&(b=a.indexOf("<"),h+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),d.chars&&d.chars(s(h)))}if(a==n)throw M("badparse",a);n=a}e()}function s(a){if(!a)return"";var d=N.exec(a);a=d[1];var c=d[3];if(d=d[2])r.innerHTML= 9 | d.replace(//g,">")}function t(a,d){var c=!1,e=g.bind(a,a.push);return{start:function(a,m,f){a=g.lowercase(a);!c&&y[a]&&(c=a);c||!0!==D[a]||(e("<"),e(a),g.forEach(m,function(c,f){var l= 10 | g.lowercase(f),m="img"===a&&"src"===l||"background"===l;!0!==Q[l]||!0===E[l]&&!d(c,m)||(e(" "),e(f),e('="'),e(C(c)),e('"'))}),e(f?"/>":">"))},end:function(a){a=g.lowercase(a);c||!0!==D[a]||(e(""));a==c&&(c=!1)},chars:function(a){c||e(C(a))}}}var M=g.$$minErr("$sanitize"),B=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,A=/^<\/\s*([\w:-]+)[^>]*>/,H=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,L=/^]*?)>/i,J=/"]/,c=/^mailto:/;return function(e,b){function m(a){a&&l.push(F(a))}function f(a, 15 | c){l.push("');m(c);l.push("")}if(!e)return e;for(var n,h=e,l=[],k,p;n=h.match(d);)k=n[0],n[2]==n[3]&&(k="mailto:"+k),p=n.index,m(h.substr(0,p)),f(k,n[0].replace(c,"")),h=h.substring(p+n[0].length);m(h);return a(l.join(""))}}])})(window,window.angular); 16 | //# sourceMappingURL=angular-sanitize.min.js.map 17 | -------------------------------------------------------------------------------- /data/example5/web/angular_1.3.1/angular-ui-router.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * State-based routing for AngularJS 3 | * @version v0.2.11 4 | * @link http://angular-ui.github.com/ 5 | * @license MIT License, http://www.opensource.org/licenses/MIT 6 | */ 7 | "undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return J(new(J(function(){},{prototype:a})),b)}function e(a){return I(arguments,function(b){b!==a&&I(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a){if(Object.keys)return Object.keys(a);var c=[];return b.forEach(a,function(a,b){c.push(b)}),c}function h(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function i(a,b,c,d){var e,i=f(c,d),j={},k=[];for(var l in i)if(i[l].params&&(e=g(i[l].params),e.length))for(var m in e)h(k,e[m])>=0||(k.push(e[m]),j[e[m]]=a[e[m]]);return J({},j,b)}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(o[c]=d,F(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);I(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return G(a)&&a.then&&a.$$promises}if(!G(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return I(g,k),g=n=o=null,function(d,f,g){function h(){--s||(t||e(r,f.$$values),p.$$values=r,p.$$promises=!0,delete p.$$inheritedValues,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!D(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;I(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!G(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=J({},d),s=1+m.length/3,t=!1;if(D(f.$$failure))return k(f.$$failure),p;f.$$inheritedValues&&e(r,f.$$inheritedValues),f.$$values?(t=e(r,f.$$values),p.$$inheritedValues=f.$$values,h()):(f.$$inheritedValues&&(p.$$inheritedValues=f.$$inheritedValues),J(q,f.$$promises),f.then(h,k));for(var u=0,v=m.length;v>u;u+=3)d.hasOwnProperty(m[u])?h():n(m[u],m[u+1],m[u+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function m(a,b,c){this.fromConfig=function(a,b,c){return D(a.template)?this.fromString(a.template,b):D(a.templateUrl)?this.fromUrl(a.templateUrl,b):D(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return E(a)?a(b):a},this.fromUrl=function(c,d){return E(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function n(a,d){function e(a){return D(a)?this.type.decode(a):p.$$getDefaultValue(this)}function f(b,c,d){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(n[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");n[b]=J({type:c||new o,$value:e},d)}function g(a,b,c){var d=a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&");if(!b)return d;var e=c?"?":"";return d+e+"("+b+")"+e}function h(a){if(!d.params||!d.params[a])return{};var b=d.params[a];return G(b)?b:{value:b}}d=b.isObject(d)?d:{};var i,j=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,k="^",l=0,m=this.segments=[],n=this.params={};this.source=a;for(var q,r,s,t,u;(i=j.exec(a))&&(q=i[2]||i[3],r=i[4]||("*"==i[1]?".*":"[^/]*"),s=a.substring(l,i.index),t=this.$types[r]||new o({pattern:new RegExp(r)}),u=h(q),!(s.indexOf("?")>=0));)k+=g(s,t.$subPattern(),D(u.value)),f(q,t,u),m.push(s),l=j.lastIndex;s=a.substring(l);var v=s.indexOf("?");if(v>=0){var w=this.sourceSearch=s.substring(v);s=s.substring(0,v),this.sourcePath=a.substring(0,l+v),I(w.substring(1).split(/[&?]/),function(a){f(a,null,h(a))})}else this.sourcePath=a,this.sourceSearch="";k+=g(s)+(d.strict===!1?"/?":"")+"$",m.push(s),this.regexp=new RegExp(k,d.caseInsensitive?"i":c),this.prefix=m[0]}function o(a){J(this,a)}function p(){function a(){return{strict:f,caseInsensitive:e}}function b(a){return E(a)||H(a)&&E(a[a.length-1])}function c(){I(h,function(a){if(n.prototype.$types[a.name])throw new Error("A type named '"+a.name+"' has already been defined.");var c=new o(b(a.def)?d.invoke(a.def):a.def);n.prototype.$types[a.name]=c})}var d,e=!1,f=!0,g=!0,h=[],i={"int":{decode:function(a){return parseInt(a,10)},is:function(a){return D(a)?this.decode(a.toString())===a:!1},pattern:/\d+/},bool:{encode:function(a){return a?1:0},decode:function(a){return 0===parseInt(a,10)?!1:!0},is:function(a){return a===!0||a===!1},pattern:/0|1/},string:{pattern:/[^\/]*/},date:{equals:function(a,b){return a.toISOString()===b.toISOString()},decode:function(a){return new Date(a)},encode:function(a){return[a.getFullYear(),("0"+(a.getMonth()+1)).slice(-2),("0"+a.getDate()).slice(-2)].join("-")},pattern:/[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/}};p.$$getDefaultValue=function(a){if(!b(a.value))return a.value;if(!d)throw new Error("Injectable functions cannot be called at configuration time");return d.invoke(a.value)},this.caseInsensitive=function(a){e=a},this.strictMode=function(a){f=a},this.compile=function(b,c){return new n(b,J(a(),c))},this.isMatcher=function(a){if(!G(a))return!1;var b=!0;return I(n.prototype,function(c,d){E(c)&&(b=b&&D(a[d])&&E(a[d]))}),b},this.type=function(a,b){return D(b)?(h.push({name:a,def:b}),g||c(),this):n.prototype.$types[a]},this.$get=["$injector",function(a){return d=a,g=!1,n.prototype.$types={},c(),I(i,function(a,b){n.prototype.$types[b]||(n.prototype.$types[b]=new o(a))}),this}]}function q(a,b){function d(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function e(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function f(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return D(d)?d:!0}function g(b,c,d,e){function f(a,b,c){return"/"===m?a:b?m.slice(0,-1)+a:c?m.slice(1)+a:a}function g(a){function c(a){var c=a(d,b);return c?(F(c)&&b.replace().url(c),!0):!1}if(!a||!a.defaultPrevented){var e,f=i.length;for(e=0;f>e;e++)if(c(i[e]))return;j&&c(j)}}function l(){return h=h||c.$on("$locationChangeSuccess",g)}var m=e.baseHref(),n=b.url();return k||l(),{sync:function(){g()},listen:function(){return l()},update:function(a){return a?void(n=b.url()):void(b.url()!==n&&(b.url(n),b.replace()))},push:function(a,c,d){b.url(a.format(c||{})),d&&d.replace&&b.replace()},href:function(c,d,e){if(!c.validates(d))return null;var g=a.html5Mode(),h=c.format(d);if(e=e||{},g||null===h||(h="#"+a.hashPrefix()+h),h=f(h,g,e.absolute),!e.absolute||!h)return h;var i=!g&&h?"/":"",j=b.port();return j=80===j||443===j?"":":"+j,[b.protocol(),"://",b.host(),j,i,h].join("")}}}var h,i=[],j=null,k=!1;this.rule=function(a){if(!E(a))throw new Error("'rule' must be a function");return i.push(a),this},this.otherwise=function(a){if(F(a)){var b=a;a=function(){return b}}else if(!E(a))throw new Error("'rule' must be a function");return j=a,this},this.when=function(a,c){var g,h=F(c);if(F(a)&&(a=b.compile(a)),!h&&!E(c)&&!H(c))throw new Error("invalid 'handler' in when()");var i={matcher:function(a,c){return h&&(g=b.compile(c),c=["$match",function(a){return g.format(a)}]),J(function(b,d){return f(b,c,a.exec(d.path(),d.search()))},{prefix:F(a.prefix)?a.prefix:""})},regex:function(a,b){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=b,b=["$match",function(a){return e(g,a)}]),J(function(c,d){return f(c,b,a.exec(d.path()))},{prefix:d(a)})}},j={matcher:b.isMatcher(a),regex:a instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](a,c));throw new Error("invalid 'what' in when()")},this.deferIntercept=function(a){a===c&&(a=!0),k=a},this.$get=g,g.$inject=["$location","$rootScope","$injector","$browser"]}function r(a,e){function f(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function h(a,b){if(!a)return c;var d=F(a),e=d?a:a.name,g=f(e);if(g){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=v[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function l(a,b){w[a]||(w[a]=[]),w[a].push(b)}function m(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!F(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(v.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):F(b.parent)?b.parent:"";if(e&&!v[e])return l(e,b.self);for(var f in y)E(y[f])&&(b[f]=y[f](b,y.$delegates[f]));if(v[c]=b,!b[x]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){u.$current.navigable==b&&j(a,c)||u.transitionTo(b,a,{location:!1})}]),w[c])for(var g=0;g-1}function o(a){var b=a.split("."),c=u.$current.name.split(".");if("**"===b[0]&&(c=c.slice(c.indexOf(b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(c.indexOf(b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length)return!1;for(var d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return c.join("")===b.join("")}function p(a,b){return F(a)&&!D(b)?y[a]:E(b)&&F(a)?(y[a]&&!y.$delegates[a]&&(y.$delegates[a]=y[a]),y[a]=b,this):this}function q(a,b){return G(a)?b=a:b.name=a,m(b),this}function r(a,e,f,l,m,p,q){function r(b,c,d,f){var g=a.$broadcast("$stateNotFound",b,c,d);if(g.defaultPrevented)return q.update(),A;if(!g.retry)return null;if(f.$retry)return q.update(),B;var h=u.transition=e.when(g.retry);return h.then(function(){return h!==u.transition?y:(b.options.$retry=!0,u.transitionTo(b.to,b.toParams,b.options))},function(){return A}),q.update(),h}function w(a,c,d,h,i){var j=d?c:k(g(a.params),c),n={$stateParams:j};i.resolve=m.resolve(a.resolve,n,i.resolve,a);var o=[i.resolve.then(function(a){i.globals=a})];return h&&o.push(h),I(a.views,function(c,d){var e=c.resolve&&c.resolve!==a.resolve?c.resolve:{};e.$template=[function(){return f.load(d,{view:c,locals:n,params:j})||""}],o.push(m.resolve(e,n,i.resolve,a).then(function(f){if(E(c.controllerProvider)||H(c.controllerProvider)){var g=b.extend({},e,n);f.$$controller=l.invoke(c.controllerProvider,null,g)}else f.$$controller=c.controller;f.$$state=a,f.$$controllerAs=c.controllerAs,i[d]=f}))}),e.all(o).then(function(){return i})}var y=e.reject(new Error("transition superseded")),z=e.reject(new Error("transition prevented")),A=e.reject(new Error("transition aborted")),B=e.reject(new Error("transition failed"));return t.locals={resolve:null,globals:{$stateParams:{}}},u={params:{},current:t.self,$current:t,transition:null},u.reload=function(){u.transitionTo(u.current,p,{reload:!0,inherit:!1,notify:!1})},u.go=function(a,b,c){return u.transitionTo(a,b,J({inherit:!0,relative:u.$current},c))},u.transitionTo=function(b,c,f){c=c||{},f=J({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var m,n=u.$current,o=u.params,v=n.path,A=h(b,f.relative);if(!D(A)){var B={to:b,toParams:c,options:f},C=r(B,n.self,o,f);if(C)return C;if(b=B.to,c=B.toParams,f=B.options,A=h(b,f.relative),!D(A)){if(!f.relative)throw new Error("No such state '"+b+"'");throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'")}}if(A[x])throw new Error("Cannot transition to abstract state '"+b+"'");f.inherit&&(c=i(p,c||{},u.$current,A)),b=A;var E=b.path,F=0,G=E[F],H=t.locals,I=[];if(!f.reload)for(;G&&G===v[F]&&j(c,o,G.ownParams);)H=I[F]=G.locals,F++,G=E[F];if(s(b,n,H,f))return b.self.reloadOnSearch!==!1&&q.update(),u.transition=null,e.when(u.current);if(c=k(g(b.params),c||{}),f.notify&&a.$broadcast("$stateChangeStart",b.self,c,n.self,o).defaultPrevented)return q.update(),z;for(var L=e.when(H),M=F;M=F;d--)g=v[d],g.self.onExit&&l.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=F;d=0?c:c+"@"+(b?b.state.name:"")}function x(a,b){var c,d=a.match(/^\s*({[^}]*})\s*$/);if(d&&(a=b+"("+d[1]+")"),c=a.replace(/\n/g," ").match(/^([^(]+?)\s*(\((.*)\))?$/),!c||4!==c.length)throw new Error("Invalid state ref '"+a+"'");return{state:c[1],paramExpr:c[3]||null}}function y(a){var b=a.parent().inheritedData("$uiView");return b&&b.state&&b.state.name?b.state:void 0}function z(a,c){var d=["location","inherit","reload"];return{restrict:"A",require:["?^uiSrefActive","?^uiSrefActiveEq"],link:function(e,f,g,h){var i=x(g.uiSref,a.current.name),j=null,k=y(f)||a.$current,l="FORM"===f[0].nodeName,m=l?"action":"href",n=!0,o={relative:k,inherit:!0},p=e.$eval(g.uiSrefOpts)||{};b.forEach(d,function(a){a in p&&(o[a]=p[a])});var q=function(b){if(b&&(j=b),n){var c=a.href(i.state,j,o),d=h[1]||h[0];return d&&d.$$setStateInfo(i.state,j),null===c?(n=!1,!1):void(f[0][m]=c)}};i.paramExpr&&(e.$watch(i.paramExpr,function(a){a!==j&&q(a)},!0),j=e.$eval(i.paramExpr)),q(),l||f.bind("click",function(b){var d=b.which||b.button;if(!(d>1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target"))){var e=c(function(){a.go(i.state,j,o)});b.preventDefault(),b.preventDefault=function(){c.cancel(e)}}})}}}function A(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(d,e,f){function g(){h()?e.addClass(m):e.removeClass(m)}function h(){return"undefined"!=typeof f.uiSrefActiveEq?a.$current.self===k&&i():a.includes(k.name)&&i()}function i(){return!l||j(l,b)}var k,l,m;m=c(f.uiSrefActiveEq||f.uiSrefActive||"",!1)(d),this.$$setStateInfo=function(b,c){k=a.get(b,y(e)),l=c,g()},d.$on("$stateChangeSuccess",g)}]}}function B(a){return function(b){return a.is(b)}}function C(a){return function(b){return a.includes(b)}}var D=b.isDefined,E=b.isFunction,F=b.isString,G=b.isObject,H=b.isArray,I=b.forEach,J=b.extend,K=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),l.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",l),m.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",m),n.prototype.concat=function(a,b){return new n(this.sourcePath+a+this.sourceSearch,b)},n.prototype.toString=function(){return this.source},n.prototype.exec=function(a,b){var c=this.regexp.exec(a);if(!c)return null;b=b||{};var d,e,f,g=this.parameters(),h=g.length,i=this.segments.length-1,j={};if(i!==c.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(d=0;i>d;d++)f=g[d],e=this.params[f],j[f]=e.$value(c[d+1]);for(;h>d;d++)f=g[d],e=this.params[f],j[f]=e.$value(b[f]);return j},n.prototype.parameters=function(a){return D(a)?this.params[a]||null:g(this.params)},n.prototype.validates=function(a){var b,c,d=!0,e=this;return I(a,function(a,f){e.params[f]&&(c=e.params[f],b=!a&&D(c.value),d=d&&(b||c.type.is(a)))}),d},n.prototype.format=function(a){var b=this.segments,c=this.parameters();if(!a)return b.join("").replace("//","/");var d,e,f,g,h,i,j=b.length-1,k=c.length,l=b[0];if(!this.validates(a))return null;for(d=0;j>d;d++)g=c[d],f=a[g],h=this.params[g],(D(f)||"/"!==b[d]&&"/"!==b[d+1])&&(null!=f&&(l+=encodeURIComponent(h.type.encode(f))),l+=b[d+1]);for(;k>d;d++)g=c[d],f=a[g],null!=f&&(i=H(f),i&&(f=f.map(encodeURIComponent).join("&"+g+"=")),l+=(e?"&":"?")+g+"="+(i?f:encodeURIComponent(f)),e=!0);return l},n.prototype.$types={},o.prototype.is=function(){return!0},o.prototype.encode=function(a){return a},o.prototype.decode=function(a){return a},o.prototype.equals=function(a,b){return a==b},o.prototype.$subPattern=function(){var a=this.pattern.toString();return a.substr(1,a.length-2)},o.prototype.pattern=/.*/,b.module("ui.router.util").provider("$urlMatcherFactory",p),q.$inject=["$locationProvider","$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",q),r.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",r),s.$inject=[],b.module("ui.router.state").provider("$view",s),b.module("ui.router.state").provider("$uiViewScroll",t),u.$inject=["$state","$injector","$uiViewScroll"],v.$inject=["$compile","$controller","$state"],b.module("ui.router.state").directive("uiView",u),b.module("ui.router.state").directive("uiView",v),z.$inject=["$state","$timeout"],A.$inject=["$state","$stateParams","$interpolate"],b.module("ui.router.state").directive("uiSref",z).directive("uiSrefActive",A).directive("uiSrefActiveEq",A),B.$inject=["$state"],C.$inject=["$state"],b.module("ui.router.state").filter("isState",B).filter("includedByState",C)}(window,window.angular); -------------------------------------------------------------------------------- /data/example5/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/data/example5/web/favicon.ico -------------------------------------------------------------------------------- /data/example5/web/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/data/example5/web/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /data/example5/web/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/data/example5/web/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /data/example5/web/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/data/example5/web/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /data/example5/web/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/data/example5/web/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /data/example5/web/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/data/example5/web/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /data/example5/web/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/data/example5/web/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /data/example5/web/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/data/example5/web/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /data/example5/web/spa/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | (function() { 4 | /* App Module */ 5 | angular.module("classreloading.app", [ 6 | 'ui.router', 7 | 'ui.bootstrap', 8 | 'classreloading.hello' 9 | ]) 10 | 11 | .run(function ($rootScope, $state, $stateParams) { 12 | // It's very handy to add references to $state and $stateParams to the $rootScope 13 | // so that you can access them from any scope within your applications.For example, 14 | //
  • will set the
  • 15 | // to active whenever 'contacts.list' or one of its decendents is active. 16 | $rootScope.$state = $state; 17 | $rootScope.$stateParams = $stateParams; 18 | 19 | $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ 20 | window.scrollTo(0, 0); 21 | }) 22 | }) 23 | 24 | .config(function ($stateProvider, $urlRouterProvider) { 25 | $urlRouterProvider 26 | // If the url is ever invalid, e.g. '/asdf', then redirect to '/' aka the home state 27 | .otherwise("/hello"); 28 | }) 29 | 30 | .config(function ($locationProvider) { 31 | $locationProvider.html5Mode(false).hashPrefix("!"); 32 | }) 33 | 34 | ; 35 | })(); 36 | -------------------------------------------------------------------------------- /data/example5/web/spa/hello/AddContactModal.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | h3.modal-title Add a new contact 3 | .modal-body 4 | form(name="form") 5 | .form-group 6 | label.control-label Contact name 7 | input.form-control(ng-model="contact.name",required,autofocus) 8 | .form-group 9 | label.control-label Phone No 10 | input.form-control(ng-model="contact.phone",required) 11 | .modal-footer 12 | button.btn.btn-default(ng-click="add()",ng-disabled="form.$invalid",style="min-width: 150px") Add 13 | -------------------------------------------------------------------------------- /data/example5/web/spa/hello/Hello.jade: -------------------------------------------------------------------------------- 1 | h1 Hello World 2 | 3 | .lead Here are contacts that we have in database: 4 | 5 | table.table.table-striped.table-hover 6 | thead 7 | tr 8 | th(style="width: 15px") # 9 | th Name 10 | th Phone 11 | th 12 | 13 | tbody 14 | tr(ng-if="!contacts.length") 15 | td.text-center.text-muted(colspan="4") No record(s) 16 | tr(ng-repeat="contact in contacts") 17 | td.text-right(style="width: 20px") {{ $index + 1 }} 18 | td {{ contact.name }} 19 | td {{ contact.phone }} 20 | td.text-right 21 | a.text-danger(href="",ng-click="remove(contact)") 22 | span.glyphicon.glyphicon-remove 23 | 24 | button.btn.btn-success(ng-click="showAddForm()") 25 | span.glyphicon.glyphicon-plus 26 | | Add new contact -------------------------------------------------------------------------------- /data/example5/web/spa/hello/Hello.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | (function() { 4 | 5 | angular.module('classreloading.hello', [ 6 | ]) 7 | 8 | .config(function ($stateProvider) { 9 | 10 | $stateProvider 11 | .state('hello', { 12 | url: '/hello', 13 | templateUrl: "/hello/Hello.jade?v=" + App.version, 14 | controller: "classreloading.hello.Ctrl" 15 | }) 16 | ; 17 | }) 18 | 19 | .controller("classreloading.hello.Ctrl", function($scope, $modal, ContactService) { 20 | ContactService.getAll().success(function(contacts) { 21 | $scope.contacts = contacts; 22 | }); 23 | 24 | $scope.showAddForm = function() { 25 | $modal.open({ 26 | templateUrl: "/hello/AddContactModal.jade", 27 | controller: "classreloading.hello.AddContactModalCtrl" 28 | }) 29 | .result.then(function(contact) { 30 | $scope.contacts.push(contact); 31 | }); 32 | }; 33 | 34 | $scope.remove = function(contact) { 35 | if (!confirm("Remove this contact: " + contact.name + " - " + contact.phone + "?")) { 36 | return; 37 | } 38 | 39 | ContactService.remove(contact).success(function() { 40 | $scope.contacts.splice($scope.contacts.indexOf(contact), 1); 41 | }); 42 | }; 43 | 44 | }) 45 | 46 | .controller("classreloading.hello.AddContactModalCtrl", function($scope, ContactService, $modalInstance) { 47 | $scope.contact = {}; 48 | $scope.add = function() { 49 | ContactService.add($scope.contact).success(function(contact1) { 50 | $modalInstance.close(contact1); 51 | }); 52 | }; 53 | }) 54 | 55 | 56 | .factory('ContactService', function ($http) { 57 | return { 58 | getAll : function() { 59 | return $http.post("/contact?action=getAll", 0); 60 | }, 61 | add : function(contact) { 62 | return $http.post("/contact?action=add", contact); 63 | }, 64 | remove : function(contact) { 65 | return $http.post("/contact?action=remove&id=" + contact.id, contact); 66 | } 67 | }; 68 | }) 69 | ; 70 | 71 | })(); -------------------------------------------------------------------------------- /data/example5/web/spa/spa.jade: -------------------------------------------------------------------------------- 1 | !!! 2 | html(ng-app="classreloading.app",lang="en") 3 | head 4 | title Little Phone Book 5 | meta(name="viewport",content="width=device-width, initial-scale=1") 6 | 7 | meta(charset="UTF-8") 8 | link(rel="stylesheet",href="/bootstrap_3.2.0/bootstrap.min.css") 9 | 10 | script(src="/jquery-2.1.1/jquery-2.1.1.min.js") 11 | script(src="/bootstrap_3.2.0/bootstrap.min.js") 12 | 13 | script(src="/angular_1.3.1/angular.min.js") 14 | script(src="/angular_1.3.1/angular-resource.min.js") 15 | script(src="/angular_1.3.1/angular-sanitize.min.js") 16 | script(src="/angular_1.3.1/angular-animate.min.js") 17 | 18 | script(src="/angular_1.3.1/angular-ui-router.min.js") 19 | 20 | script(src="/bootstrap_3.2.0/ui-bootstrap-tpls-0.11.2.min.js") 21 | 22 | // spa-js 23 | 24 | script 25 | var App = App || {}; 26 | App.version = "#{version}"; 27 | 28 | body 29 | .container(ui-view) 30 | // 31 | pre 32 | div $state = {{$state.current.name}} 33 | div $stateParams = {{$stateParams}} 34 | div $state full url = {{ $state.$current.url.source }} -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | qj.blog 5 | classreloading 6 | 1.0-SNAPSHOT 7 | 8 | 9 | 10 | org.apache.maven.plugins 11 | maven-compiler-plugin 12 | 3.2 13 | 14 | 1.8 15 | 1.8 16 | 17 | 18 | 19 | 20 | 21 | 22 | de.neuland-bfi 23 | jade4j 24 | 0.4.0 25 | 26 | 27 | javax 28 | javaee-api 29 | 7.0 30 | 31 | 32 | org.eclipse.jetty 33 | jetty-server 34 | 9.2.2.v20140723 35 | 36 | 37 | org.eclipse.jetty 38 | jetty-servlet 39 | 9.2.2.v20140723 40 | 41 | 42 | org.eclipse.jetty 43 | jetty-webapp 44 | 9.2.2.v20140723 45 | test 46 | 47 | 48 | org.xerial 49 | sqlite-jdbc 50 | 3.8.7 51 | 52 | 53 | 54 | com.google.code.gson 55 | gson 56 | 2.3 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /push_github.cmd: -------------------------------------------------------------------------------- 1 | git push origin master -------------------------------------------------------------------------------- /run_example1.bat: -------------------------------------------------------------------------------- 1 | mvn exec:java -Dexec.mainClass="qj.blog.classreloading.example1.StaticInt" -------------------------------------------------------------------------------- /run_example2.bat: -------------------------------------------------------------------------------- 1 | mvn exec:java -Dexec.mainClass="qj.blog.classreloading.example2.ReloadingContinuously" -------------------------------------------------------------------------------- /run_example3.bat: -------------------------------------------------------------------------------- 1 | mvn exec:java -Dexec.mainClass="qj.blog.classreloading.example3.ContextReloading" -------------------------------------------------------------------------------- /run_example4.bat: -------------------------------------------------------------------------------- 1 | mvn exec:java -Dexec.mainClass="qj.blog.classreloading.example4.KeepConnectionPool" -------------------------------------------------------------------------------- /run_example5.bat: -------------------------------------------------------------------------------- 1 | mvn exec:java -Dexec.mainClass="qj.blog.classreloading.example5.LittlePhoneBookMain" -------------------------------------------------------------------------------- /src/main/ai/1_2_loaders.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/src/main/ai/1_2_loaders.ai -------------------------------------------------------------------------------- /src/main/ai/2_many_loaders.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/src/main/ai/2_many_loaders.ai -------------------------------------------------------------------------------- /src/main/ai/3_context_reloading.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/src/main/ai/3_context_reloading.ai -------------------------------------------------------------------------------- /src/main/ai/4_persisting_connection.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/src/main/ai/4_persisting_connection.ai -------------------------------------------------------------------------------- /src/main/ai/5_reloading_web_context.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/src/main/ai/5_reloading_web_context.ai -------------------------------------------------------------------------------- /src/main/ai/5_web_app.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quanla/classreloading/efafff7686187a4b7d052bc3de0c878ad508e931/src/main/ai/5_web_app.ai -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example1/StaticInt.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example1; 2 | 3 | import static java.lang.System.*; 4 | 5 | import qj.util.ReflectUtil; 6 | import qj.util.lang.DynamicClassLoader; 7 | 8 | /** 9 | * Created by Quan on 26/10/2014. 10 | */ 11 | public class StaticInt { 12 | public static void main(String[] args) { 13 | Class userClass1 = User.class; 14 | Class userClass2 = new DynamicClassLoader("target/classes") 15 | .load("qj.blog.classreloading.example1.StaticInt$User"); 16 | 17 | out.println("Seems to be the same class:"); 18 | out.println(userClass1.getName()); 19 | out.println(userClass2.getName()); 20 | out.println(); 21 | 22 | out.println("But why there are 2 different class loaders:"); 23 | out.println(userClass1.getClassLoader()); 24 | out.println(userClass2.getClassLoader()); 25 | out.println(); 26 | 27 | User.age = 11; 28 | out.println("And different age values:"); 29 | out.println((int) ReflectUtil.getStaticFieldValue("age", userClass1)); 30 | out.println((int) ReflectUtil.getStaticFieldValue("age", userClass2)); 31 | } 32 | 33 | public static class User { 34 | public static int age = 10; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example2/ReloadingContinuously.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example2; 2 | 3 | import qj.util.ReflectUtil; 4 | import qj.util.ThreadUtil; 5 | import qj.util.lang.DynamicClassLoader; 6 | 7 | /** 8 | * Created by Quan on 31/10/2014. 9 | */ 10 | public class ReloadingContinuously { 11 | public static void main(String[] args) { 12 | for (;;) { 13 | Class userClass = new DynamicClassLoader("target/classes") 14 | .load("qj.blog.classreloading.example2.ReloadingContinuously$User"); 15 | ReflectUtil.invokeStatic("hobby", userClass); 16 | ThreadUtil.sleep(2000); 17 | } 18 | } 19 | 20 | @SuppressWarnings("UnusedDeclaration") 21 | public static class User { 22 | public static void hobby() { 23 | playFootball(); // Will comment during runtime 24 | // playBasketball(); // Will uncomment during runtime 25 | } 26 | 27 | // Will comment during runtime 28 | public static void playFootball() { 29 | System.out.println("Play Football"); 30 | } 31 | 32 | // Will uncomment during runtime 33 | // public static void playBasketball() { 34 | // System.out.println("Play Basketball"); 35 | // } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example3/ContextReloading.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example3; 2 | 3 | import static qj.util.ReflectUtil.*; 4 | import qj.util.ThreadUtil; 5 | import qj.util.lang.DynamicClassLoader; 6 | 7 | /** 8 | * Created by Quan on 31/10/2014. 9 | */ 10 | public class ContextReloading { 11 | public static void main(String[] args) { 12 | for (;;) { 13 | Object context = createContext(); 14 | invokeHobbyService(context); 15 | ThreadUtil.sleep(2000); 16 | } 17 | } 18 | 19 | private static Object createContext() { 20 | Class contextClass = new DynamicClassLoader("target/classes") 21 | .load("qj.blog.classreloading.example3.ContextReloading$Context"); 22 | Object context = newInstance(contextClass); 23 | invoke("init", context); 24 | return context; 25 | } 26 | 27 | private static void invokeHobbyService(Object context) { 28 | Object hobbyService = getFieldValue("hobbyService", context); 29 | invoke("hobby", hobbyService); 30 | } 31 | 32 | @SuppressWarnings("UnusedDeclaration") 33 | public static class Context { 34 | public HobbyService hobbyService = new HobbyService(); 35 | 36 | public void init() { 37 | // Init your services here 38 | hobbyService.user = new User(); 39 | } 40 | } 41 | 42 | public static class HobbyService { 43 | 44 | public User user; 45 | 46 | public void hobby() { 47 | user.hobby(); 48 | } 49 | } 50 | 51 | public static class User { 52 | public static void hobby() { 53 | // playFootball(); // Will comment during runtime 54 | playBasketball(); // Will uncomment during runtime 55 | } 56 | 57 | // Will comment during runtime 58 | // public static void playFootball() { 59 | // System.out.println("Play Football"); 60 | // } 61 | 62 | // Will uncomment during runtime 63 | public static void playBasketball() { 64 | System.out.println("Play Basketball"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example4/KeepConnectionPool.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example4; 2 | 3 | import static qj.util.ReflectUtil.*; 4 | 5 | import qj.blog.classreloading.example4.crossing.ConnectionPool; 6 | import qj.util.ThreadUtil; 7 | import qj.util.lang.ExceptingClassLoader; 8 | 9 | /** 10 | * Created by Quan on 01/11/2014. 11 | */ 12 | public class KeepConnectionPool { 13 | public static void main(String[] args) { 14 | 15 | ConnectionPool pool = new ConnectionPool(); 16 | 17 | for (;;) { 18 | Object context = createContext(pool); 19 | 20 | invokeService(context); 21 | 22 | ThreadUtil.sleep(2000); 23 | } 24 | } 25 | 26 | private static Object createContext(ConnectionPool pool) { 27 | ExceptingClassLoader classLoader = new ExceptingClassLoader( 28 | (className) -> className.contains(".crossing."), 29 | "target/classes"); 30 | Class contextClass = classLoader.load("qj.blog.classreloading.example4.reloadable.Context"); 31 | Object context = newInstance(contextClass); 32 | 33 | setFieldValue(pool, "pool", context); 34 | invoke("init", context); 35 | 36 | return context; 37 | } 38 | 39 | private static void invokeService(Object context) { 40 | Object hobbyService = getFieldValue("userService", context); 41 | invoke("hello", hobbyService); 42 | } 43 | 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example4/crossing/Connection.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example4.crossing; 2 | 3 | public class Connection { 4 | public String getUserName() { 5 | // System.out.println("Connection CL: " + this.getClass().getClassLoader()); // Will output DefaultClassLoader 6 | return "Joe"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example4/crossing/ConnectionPool.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example4.crossing; 2 | 3 | public class ConnectionPool { 4 | Connection conn = new Connection(); 5 | 6 | public Connection getConnection() { 7 | return conn; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example4/reloadable/Context.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example4.reloadable; 2 | 3 | import qj.blog.classreloading.example4.crossing.ConnectionPool; 4 | 5 | @SuppressWarnings("UnusedDeclaration") 6 | public class Context { 7 | public ConnectionPool pool; 8 | 9 | public UserService userService = new UserService(); 10 | 11 | public void init() { 12 | userService.pool = pool; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example4/reloadable/UserService.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example4.reloadable; 2 | 3 | import qj.blog.classreloading.example4.crossing.ConnectionPool; 4 | 5 | public class UserService { 6 | ConnectionPool pool; 7 | 8 | @SuppressWarnings("UnusedDeclaration") 9 | public void hello() { 10 | // System.out.println("UserService CL: " + this.getClass().getClassLoader()); // Will output ExceptingClassLoader 11 | System.out.println("Hi " + pool.getConnection().getUserName()); 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example5/LittlePhoneBookMain.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example5; 2 | 3 | import org.eclipse.jetty.server.Server; 4 | import org.eclipse.jetty.servlet.FilterHolder; 5 | import org.eclipse.jetty.servlet.ServletContextHandler; 6 | import org.eclipse.jetty.servlet.ServletHolder; 7 | import qj.tool.web.ReloadingWebContext; 8 | import qj.tool.web.ResourceFilter; 9 | import qj.util.PropertiesUtil; 10 | import qj.util.SystemUtil; 11 | import qj.util.ThreadUtil; 12 | import qj.util.funct.F0; 13 | import qj.util.funct.P0; 14 | import qj.util.lang.ExceptingClassLoader; 15 | 16 | import javax.servlet.DispatcherType; 17 | import javax.servlet.ServletException; 18 | import javax.servlet.http.HttpServlet; 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.io.IOException; 22 | import java.sql.Connection; 23 | import java.sql.DriverManager; 24 | import java.sql.SQLException; 25 | import java.sql.Statement; 26 | import java.util.EnumSet; 27 | import java.util.Properties; 28 | 29 | public class LittlePhoneBookMain { 30 | public static boolean development = true; 31 | public static String version = "1.0.0"; 32 | 33 | public static void main(String[] args) throws Exception { 34 | Properties config = PropertiesUtil.loadPropertiesFromFile("data/example5/config.properties"); 35 | 36 | startServer(config); 37 | } 38 | 39 | public static void startServer(Properties config) throws Exception { 40 | final ServerControl webServer = startWebServer(config); 41 | 42 | // Console commands are used to control server 43 | SystemUtil.onReturn(line -> { 44 | // Type exit then enter to stop the server 45 | if ("exit".equals(line)) { 46 | System.out.print("Stopping web server..."); 47 | webServer.closeF.run(); 48 | System.out.print(" done."); 49 | System.exit(0); 50 | } 51 | }); 52 | } 53 | 54 | public static ServerControl startWebServer(Properties config) throws Exception { 55 | int port = Integer.parseInt(config.getProperty("web.port")); 56 | 57 | // Create the connection pool in the persisted area 58 | DbPool dbPool = initDatabase(config); 59 | 60 | ServletContextHandler servletContext = new ServletContextHandler(ServletContextHandler.SESSIONS); 61 | servletContext.setContextPath("/"); 62 | 63 | ReloadingWebContext contextLoader = new ReloadingWebContext( 64 | "qj.blog.classreloading.example5.reloadable.Context", 65 | () -> ( development ? 66 | // During development, the dynamic class loader will be used 67 | new ExceptingClassLoader( 68 | (className) -> className.startsWith("qj.util"), 69 | "target/classes" 70 | ) : 71 | 72 | // During production, the default class loader will be used 73 | LittlePhoneBookMain.class.getClassLoader() 74 | ), 75 | development ? 76 | // During development, each time a GET to root URL "/", the dynamic context will be reloaded 77 | (req) -> req.getMethod().equalsIgnoreCase("GET") && req.getRequestURI().equals("/") : 78 | null 79 | ); 80 | 81 | // Fields to be set each time the context is reloaded 82 | contextLoader.setField("development", development); 83 | contextLoader.setField("buildVersion", version); 84 | contextLoader.setField("connF", dbPool.connF); 85 | 86 | // Method "init" will be called with param "data/example5/web" each time the context is reloaded 87 | contextLoader.initWith("init", "data/example5/web"); 88 | 89 | // Method "close" will be called each time context is un-linked ( to create and link to newer context, with 90 | // newer classes) 91 | contextLoader.beforeClose("close"); 92 | 93 | // The "stubServlet" method will provide "stub" servlets which will retrieve real servlets in the reloaded 94 | // context each time a request is served 95 | servletContext.addServlet( new ServletHolder(contextLoader.stubServlet("jade")), "/"); 96 | 97 | servletContext.addServlet( new ServletHolder(wrapServlet(contextLoader.stubServlet("contact"), dbPool.closeThreadConn)), 98 | "/contact"); 99 | 100 | servletContext.addServlet( new ServletHolder(contextLoader.stubServlet("jade")), "*.jade"); 101 | 102 | // Serve resources 103 | ResourceFilter resourceFilter = resourceFilter("data/example5/web"); 104 | servletContext.addFilter( 105 | new FilterHolder(resourceFilter), 106 | "/*", EnumSet.of(DispatcherType.REQUEST)); 107 | 108 | final Server server = new Server(port); 109 | server.setHandler(servletContext); 110 | 111 | server.start(); 112 | System.out.println("Server started on port " + port); 113 | 114 | final Runnable closeF = () -> { 115 | System.out.print("Stopping box server..."); 116 | try { 117 | server.stop(); 118 | } catch (Exception e1) { 119 | e1.printStackTrace(); 120 | } 121 | dbPool.closePool.e(); 122 | System.out.print(" done."); 123 | }; 124 | return new ServerControl(closeF); 125 | } 126 | 127 | private static DbPool initDatabase(Properties config) throws SQLException, ClassNotFoundException { 128 | DbPool dbPool = new DbPool(config); 129 | Connection connection = dbPool.connF.e(); 130 | initDb(connection); 131 | dbPool.closeThreadConn.e(); 132 | return dbPool; 133 | } 134 | 135 | public static ResourceFilter resourceFilter(String boxWebLoc) { 136 | return new ResourceFilter( 137 | req -> null, 138 | boxWebLoc 139 | ); 140 | } 141 | 142 | public static class ServerControl { 143 | Runnable closeF; 144 | 145 | public ServerControl(Runnable closeF) { 146 | this.closeF = closeF; 147 | } 148 | } 149 | 150 | /** 151 | * This is pool provide only 1 shared connection to the SQLite memory database 152 | */ 153 | static class DbPool { 154 | 155 | public F0 connF; 156 | public P0 closeThreadConn; 157 | protected P0 closePool; 158 | 159 | public DbPool(Properties config) throws SQLException, ClassNotFoundException { 160 | 161 | Class.forName(config.getProperty("db.driver")); 162 | 163 | Connection connection = DriverManager.getConnection(config.getProperty("db.url")); 164 | 165 | ThreadUtil.ThreadLocalCache threadLocal = ThreadUtil.threadLocalCache(() -> connection); 166 | connF = threadLocal.cacheF; 167 | closeThreadConn = () -> { 168 | // Connection conn = threadLocal.removeF.e(); 169 | // if (conn != null) { 170 | //// System.out.println("Closing thread ds"); 171 | // IOUtil.close(conn); 172 | // } 173 | }; 174 | 175 | closePool = () -> { 176 | //noinspection EmptyCatchBlock 177 | try { 178 | connection.close(); 179 | } catch (SQLException e1) { 180 | } 181 | }; 182 | } 183 | 184 | } 185 | 186 | /** 187 | * The SQLite memory db is initialized before use 188 | * @throws SQLException 189 | */ 190 | private static void initDb(Connection connection) throws SQLException { 191 | Statement statement = connection.createStatement(); 192 | statement.setQueryTimeout(30); // set timeout to 30 sec. 193 | 194 | statement.executeUpdate("drop table if exists contact"); 195 | statement.executeUpdate("create table contact (id integer PRIMARY KEY AUTOINCREMENT, name string, phone string)"); 196 | statement.executeUpdate("insert into contact values(1, 'Andrew King', '0648 6815 1654')"); 197 | statement.executeUpdate("insert into contact values(2, 'William Shakespeare', '0234 5234 3264')"); 198 | } 199 | 200 | private static HttpServlet wrapServlet(HttpServlet servlet, P0 closeThreadConn) { 201 | return new HttpServlet() { 202 | protected void service(HttpServletRequest req, 203 | HttpServletResponse resp) throws ServletException, 204 | IOException { 205 | try { 206 | servlet.service(req, resp); 207 | } finally { 208 | closeThreadConn.e(); 209 | } 210 | } 211 | }; 212 | } 213 | 214 | // public static class Build { 215 | // public static void main(String[] args) { 216 | // System.out.println(BuildUtil.runCommand(LittlePhoneBookMain.class)); 217 | // } 218 | // } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example5/reloadable/Context.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example5.reloadable; 2 | 3 | import qj.blog.classreloading.example5.reloadable.servlet.ContactServlet; 4 | import qj.blog.classreloading.example5.reloadable.servlet.JadeServlet; 5 | import qj.util.funct.F0; 6 | 7 | import java.sql.Connection; 8 | 9 | @SuppressWarnings("UnusedDeclaration") 10 | public class Context { 11 | public String buildVersion; 12 | public boolean development; 13 | public F0 connF; 14 | 15 | public JadeServlet jadeServlet = new JadeServlet(); 16 | public ContactServlet contactServlet = new ContactServlet(); 17 | 18 | public void init(String webLoc) { 19 | // System.out.println("Initializing context."); 20 | 21 | jadeServlet.version = buildVersion; 22 | jadeServlet.init(webLoc); 23 | 24 | contactServlet.connF = connF; 25 | } 26 | 27 | public void close() { 28 | // System.out.println("Closing context."); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example5/reloadable/dao/ContactDAO.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example5.reloadable.dao; 2 | 3 | import qj.blog.classreloading.example5.reloadable.model.Contact; 4 | import qj.tool.sql.Builder; 5 | import qj.tool.sql.Template; 6 | 7 | import java.sql.Connection; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by Quan on 22/12/2014. 12 | */ 13 | public class ContactDAO { 14 | static Template template = new Builder<>(Contact.class) 15 | .build(); 16 | 17 | public static List selectAll(Connection conn) { 18 | return template.selectAll(conn); 19 | } 20 | 21 | public static void insert(Contact contact, Connection conn) { 22 | template.insert(contact, conn); 23 | } 24 | 25 | public static void delete(Long id, Connection conn) { 26 | template.deleteById(id, conn); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example5/reloadable/model/Contact.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example5.reloadable.model; 2 | 3 | /** 4 | * Created by Quan on 22/12/2014. 5 | */ 6 | public class Contact { 7 | public Long id; 8 | public String name; 9 | public String phone; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example5/reloadable/servlet/ContactServlet.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example5.reloadable.servlet; 2 | 3 | import com.google.gson.Gson; 4 | import qj.blog.classreloading.example5.reloadable.dao.ContactDAO; 5 | import qj.blog.classreloading.example5.reloadable.model.Contact; 6 | import qj.util.funct.F0; 7 | 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServlet; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.sql.Connection; 14 | 15 | /** 16 | * Created by Quan on 22/12/2014. 17 | */ 18 | public class ContactServlet extends HttpServlet { 19 | 20 | public F0 connF; 21 | 22 | @Override 23 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 24 | String action = req.getParameter("action"); 25 | 26 | switch (action) { 27 | case "getAll": 28 | getAll(resp); 29 | break; 30 | case "add": 31 | add(req, resp); 32 | break; 33 | case "remove": 34 | remove(req, resp); 35 | break; 36 | 37 | } 38 | } 39 | 40 | private void getAll(HttpServletResponse resp) throws IOException { 41 | new Gson().toJson(ContactDAO.selectAll(connF.e()), resp.getWriter()); 42 | } 43 | 44 | private void add(HttpServletRequest req, HttpServletResponse resp) throws IOException { 45 | 46 | Gson gson = new Gson(); 47 | Contact contact = gson.fromJson(req.getReader(), Contact.class); 48 | 49 | ContactDAO.insert(contact, connF.e()); 50 | 51 | gson.toJson(contact, resp.getWriter()); 52 | } 53 | 54 | private void remove(HttpServletRequest req, HttpServletResponse resp) throws IOException { 55 | Long id = Long.valueOf(req.getParameter("id")); 56 | 57 | ContactDAO.delete(id, connF.e()); 58 | 59 | resp.getWriter().write(0); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/qj/blog/classreloading/example5/reloadable/servlet/JadeServlet.java: -------------------------------------------------------------------------------- 1 | package qj.blog.classreloading.example5.reloadable.servlet; 2 | 3 | import de.neuland.jade4j.Jade4J; 4 | import de.neuland.jade4j.model.JadeModel; 5 | import de.neuland.jade4j.template.JadeTemplate; 6 | import qj.util.Cols; 7 | import qj.util.FileUtil; 8 | import qj.util.StringUtil; 9 | import qj.util.funct.P2; 10 | import qj.util.math.Range; 11 | 12 | import javax.servlet.ServletException; 13 | import javax.servlet.http.HttpServlet; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.io.StringWriter; 19 | import java.util.LinkedList; 20 | 21 | public class JadeServlet extends HttpServlet { 22 | public String version = null; 23 | private String webLoc; 24 | 25 | @Override 26 | public void doGet(HttpServletRequest req, HttpServletResponse resp) 27 | throws ServletException, IOException { 28 | resp.setContentType("text/html; charset=UTF-8"); 29 | 30 | if (StringUtil.countHappens('.', req.getServerName()) == 1) { 31 | resp.sendRedirect("http://www." + req.getServerName()); // Fix ui-sref urls 32 | return; 33 | } 34 | 35 | String requestURI = req.getRequestURI(); 36 | if (requestURI.equals("/")) { 37 | requestURI = "/spa.jade"; 38 | } 39 | File file = new File(webLoc + "/spa" + requestURI); 40 | 41 | if (!file.exists()) { 42 | resp.sendRedirect("/#!" + requestURI.replaceFirst("/$", "")); // Fix ui-sref urls 43 | return; 44 | } 45 | 46 | JadeTemplate template = Jade4J.getTemplate(file.getPath()); 47 | if ("/spa.jade".equals(requestURI)) { 48 | StringWriter buffer = new StringWriter(); 49 | template.process(new JadeModel(Cols.map( 50 | "version", version 51 | )), buffer); 52 | 53 | String target = ""; 54 | 55 | String scriptLocations = allJs(); 56 | 57 | String content = buffer.toString(); 58 | resp.getWriter().write( 59 | StringUtil.replace(scriptLocations, Range.fromlength(content.indexOf(target), target.length()), content) 60 | ); 61 | } else { 62 | template.process(new JadeModel(null), resp.getWriter()); 63 | } 64 | } 65 | 66 | private String allJs() { 67 | LinkedList col = new LinkedList<>(); 68 | P2 collect = (file, path) -> { 69 | if (file.getName().endsWith(".js")) { 70 | if (StringUtil.isEmpty(path)) { 71 | col.add("/" + file.getName()); 72 | } else { 73 | col.add("/" + path.replaceAll("\\\\", "/") + "/" + file.getName()); 74 | } 75 | } 76 | }; 77 | FileUtil.eachFile(new File(webLoc + "/spa"), collect); 78 | 79 | return Cols.join((Iterable)Cols.yield(col, s -> ""), ""); 80 | } 81 | 82 | public void init(String webLoc) { 83 | this.webLoc = webLoc; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/qj/tool/sql/Builder.java: -------------------------------------------------------------------------------- 1 | package qj.tool.sql; 2 | 3 | import com.google.gson.Gson; 4 | import qj.tool.sql.Template.Field1; 5 | import qj.util.Cols; 6 | import qj.util.NameCaseUtil; 7 | import qj.util.ReflectUtil; 8 | import qj.util.StringUtil; 9 | import qj.util.funct.F1; 10 | import qj.util.funct.F2; 11 | import qj.util.funct.P1; 12 | 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.Method; 15 | import java.lang.reflect.Modifier; 16 | import java.lang.reflect.Type; 17 | import java.sql.Blob; 18 | import java.sql.ResultSet; 19 | import java.sql.SQLException; 20 | import java.util.*; 21 | 22 | public class Builder { 23 | 24 | private Class clazz; 25 | private List idFields = Arrays.asList("id"); 26 | private String tableName; 27 | boolean autoIncrement = true; 28 | 29 | public Builder(Class clazz) { 30 | this.clazz = clazz; 31 | 32 | tableName = NameCaseUtil.camelToHyphen(clazz.getSimpleName()); 33 | } 34 | 35 | public static void main(String[] args) { 36 | System.out.println(NameCaseUtil.camelToHyphen("ChatLastRead")); 37 | } 38 | 39 | 40 | public Builder id(String... idFields) { 41 | this.idFields = Arrays.asList(idFields); 42 | return this; 43 | } 44 | 45 | HashSet dontStore = new HashSet<>(); 46 | public Template build() { 47 | Template template = new Template<>(clazz); 48 | template.idFields = Cols.yield(idFields, (fName) -> field1(ReflectUtil.getField(fName, clazz))); 49 | template.dataFields = new LinkedList<>(); 50 | template.tableName = tableName; 51 | template.autoIncrement = autoIncrement; 52 | eachField(clazz, (f) -> { 53 | if (dontStore.contains(f.getName())) { 54 | return; 55 | } 56 | template.dataFields.add(field1(f)); 57 | }); 58 | return template; 59 | } 60 | 61 | @SuppressWarnings("UnusedDeclaration") 62 | public Builder noId() { 63 | idFields = Collections.emptyList(); 64 | return this; 65 | } 66 | public Builder tableName(String tableName) { 67 | this.tableName = tableName; 68 | return this; 69 | } 70 | 71 | public Field1 field1(Field field) { 72 | Field1 raw = field1_raw(field); 73 | F1, Field1> decor = fieldDecors.get(field.getName()); 74 | if (decor != null) { 75 | return decor.e(raw); 76 | } 77 | return raw; 78 | } 79 | 80 | public static Field1 field1_raw(Field field) { 81 | Field1 field1 = new Field1() { 82 | @Override 83 | void setValue(Object val, M m) { 84 | if (boolean.class.equals(field.getType())) { 85 | if (val == null) { 86 | val = Boolean.FALSE; 87 | } 88 | } 89 | ReflectUtil.setFieldValue(val, field, m); 90 | } 91 | @Override 92 | Object getValue(M m) { 93 | return ReflectUtil.getFieldValue(field, m); 94 | } 95 | }; 96 | field1.type = field.getGenericType(); 97 | field1.sqlName = NameCaseUtil.camelToHyphen(field.getName()); 98 | field1.psSetter = SQLUtil.setter(field.getType()); 99 | field1.rsGet = rsGet(field.getType()); 100 | return field1; 101 | } 102 | 103 | Map,Field1>> fieldDecors = new HashMap<>(); 104 | 105 | private Builder embeded(String fieldName, F1,Type> convertTypeF, 106 | F1 afterDeserialized) { 107 | fieldDecors.put(fieldName, f1 -> { 108 | Field1 newField1 = new Field1() { 109 | @Override 110 | void setValue(Object val, M m) { 111 | if (val == null || "null".equals(val)) { 112 | f1.setValue(null, m); 113 | return; 114 | } 115 | Object o = new Gson().fromJson(((String)val), convertTypeF.e(f1)); 116 | 117 | Object value = afterDeserialized==null ? o : afterDeserialized.e(o); 118 | 119 | f1.setValue(value, m); 120 | } 121 | 122 | @Override 123 | Object getValue(M m) { 124 | Object val = f1.getValue(m); 125 | return new Gson().toJson(val); 126 | } 127 | }; 128 | newField1.psSetter = SQLUtil.setter(String.class); 129 | newField1.rsGet = rsGet(String.class); 130 | newField1.sqlName = f1.sqlName; 131 | return newField1; 132 | }); 133 | return this; 134 | } 135 | 136 | public Builder embeded(String fieldName) { 137 | return embeded(fieldName, (f1) -> f1.type, null); 138 | } 139 | 140 | @SuppressWarnings("UnusedDeclaration") 141 | public Builder dontStore(String fieldName) { 142 | dontStore.add(fieldName); 143 | return this; 144 | } 145 | 146 | private void eachField(Class clazz, P1 p1) { 147 | for (final Field field : clazz.getDeclaredFields()) { 148 | int modifiers = field.getModifiers(); 149 | if ((modifiers & (Modifier.STATIC | Modifier.FINAL | Modifier.TRANSIENT)) > 0 150 | || (modifiers & Modifier.PUBLIC) == 0 151 | ) { 152 | continue; 153 | } 154 | 155 | if (idFields.contains(field.getName())) { 156 | continue; 157 | } 158 | p1.e(field); 159 | } 160 | if (!clazz.equals(Object.class)) { 161 | eachField(clazz.getSuperclass(), p1); 162 | } 163 | } 164 | 165 | private static F2 rsGet(Class type) { 166 | if (type.equals(Date.class)) { 167 | return (rs, index) -> { 168 | try { 169 | return rs.getTimestamp(index); 170 | } catch (SQLException e) { 171 | throw new RuntimeException(e); 172 | } 173 | }; 174 | } 175 | if (type.equals(byte[].class)) { 176 | return (rs, index) -> { 177 | try { 178 | Blob blob = rs.getBlob(index); 179 | return blob.getBytes(1, (int) blob.length()); 180 | } catch (SQLException e) { 181 | throw new RuntimeException(e); 182 | } 183 | }; 184 | } 185 | 186 | Method methodWasNull = ReflectUtil.getMethod("wasNull", ResultSet.class); 187 | Method methodGet = ReflectUtil.getMethod(rsGetMethodName(type), new Class[] {int.class}, ResultSet.class); 188 | return (rs, index) -> { 189 | Object val = ReflectUtil.invoke(methodGet, rs, index); 190 | Boolean wasNull = ReflectUtil.invoke(methodWasNull, rs); 191 | 192 | return wasNull ? null : val; 193 | }; 194 | } 195 | 196 | private static String rsGetMethodName(Class type) { 197 | String simpleName = type.getSimpleName(); 198 | if (simpleName.equals("Integer")) { 199 | simpleName = "Int"; 200 | } 201 | return "get" + StringUtil.upperCaseFirstChar(simpleName); 202 | } 203 | 204 | public Builder fieldConvert(String fieldName, FieldConverter converter) { 205 | fieldDecors.put(fieldName, (f1) -> { 206 | Field1 newField1 = new Field1() { 207 | @Override 208 | void setValue(Object val, M m) { 209 | if (val == null) { 210 | f1.setValue(null, m); 211 | return; 212 | } 213 | f1.setValue(converter.fromDB(val),m); 214 | } 215 | 216 | @Override 217 | Object getValue(M m) { 218 | MT val = (MT) f1.getValue(m); 219 | return converter.toDB(val); 220 | } 221 | }; 222 | newField1.psSetter = SQLUtil.setter(converter.dbType()); 223 | newField1.rsGet = rsGet(converter.dbType()); 224 | newField1.sqlName = f1.sqlName; 225 | return newField1; 226 | }); 227 | 228 | return this; 229 | } 230 | public static interface FieldConverter { 231 | 232 | MT fromDB(Object val); 233 | Object toDB(MT val); 234 | 235 | Class dbType(); 236 | 237 | } 238 | public Builder autoIncrement(boolean b) { 239 | autoIncrement = b; 240 | return this; 241 | } 242 | 243 | } -------------------------------------------------------------------------------- /src/main/java/qj/tool/sql/SQLUtil.java: -------------------------------------------------------------------------------- 1 | package qj.tool.sql; 2 | 3 | import qj.util.IOUtil; 4 | import qj.util.ReflectUtil; 5 | import qj.util.StringUtil; 6 | import qj.util.funct.P0; 7 | import qj.util.funct.P3; 8 | 9 | import javax.sql.rowset.serial.SerialBlob; 10 | import java.lang.reflect.Method; 11 | import java.sql.*; 12 | import java.util.Date; 13 | 14 | @SuppressWarnings("UnusedDeclaration") 15 | public class SQLUtil { 16 | 17 | public static void transaction(P0 run, Connection conn) { 18 | try { 19 | conn.setAutoCommit(false); 20 | run.e(); 21 | conn.commit(); 22 | conn.setAutoCommit(true); 23 | } catch (SQLException e) { 24 | try { 25 | conn.rollback(); 26 | conn.setAutoCommit(true); 27 | } catch (SQLException ignored) { 28 | } 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | public static Long selectLong(Connection conn, String query, Object... params) { 33 | PreparedStatement ps = null; 34 | ResultSet rs = null; 35 | try { 36 | ps = conn.prepareStatement(query); 37 | psSet1(params, ps); 38 | rs = ps.executeQuery(); 39 | if (rs.next()) { 40 | return rs.getLong(1); 41 | } 42 | return null; 43 | } catch (SQLException e) { 44 | throw new RuntimeException(e); 45 | } finally { 46 | IOUtil.close(rs); 47 | IOUtil.close(ps); 48 | } 49 | } 50 | public static void update(Connection conn, String query, Object... params) { 51 | PreparedStatement ps = null; 52 | try { 53 | ps = conn.prepareStatement(query); 54 | psSet1(params, ps); 55 | ps.executeUpdate(); 56 | } catch (SQLException e) { 57 | throw new RuntimeException(e); 58 | } finally { 59 | IOUtil.close(ps); 60 | } 61 | } 62 | public static P3 setter(Class type) { 63 | if (type.equals(Date.class)) { 64 | return (ps, index, val) -> { 65 | try { 66 | if (val==null) { 67 | ps.setNull(index, Types.DATE); 68 | } else { 69 | ps.setTimestamp(index, new Timestamp(((Date)val).getTime())); 70 | } 71 | } catch (SQLException e) { 72 | throw new RuntimeException(e); 73 | } 74 | }; 75 | } 76 | if (type.equals(boolean.class)) { 77 | return (ps, index, val) -> { 78 | try { 79 | ps.setInt(index, (Boolean) val ? 1 : 0); 80 | } catch (SQLException e) { 81 | throw new RuntimeException(e); 82 | } 83 | }; 84 | } 85 | if (type.equals(byte[].class)) { 86 | return (ps, index, val) -> { 87 | try { 88 | ps.setBlob(index, new SerialBlob((byte[])val)); 89 | } catch (SQLException e) { 90 | throw new RuntimeException(e); 91 | } 92 | }; 93 | } 94 | String methodName = "set" + StringUtil.upperCaseFirstChar(type.getSimpleName()); 95 | Method method = ReflectUtil.findMethod(PreparedStatement.class, (m) -> m.getName().equals(methodName) && m.getParameterCount() == 2); 96 | if (method == null) { 97 | return null; 98 | } 99 | return (ps, index, val) -> { 100 | 101 | if (val==null) { 102 | try { 103 | ps.setNull(index, Types.VARCHAR); 104 | } catch (SQLException e) { 105 | throw new RuntimeException(e); 106 | } 107 | } else { 108 | // System.out.println(method.getName()); 109 | ReflectUtil.invoke(method, ps, index, val); 110 | } 111 | }; 112 | } 113 | static int psSet(Object[] params, PreparedStatement ps, int index) 114 | throws SQLException { 115 | if (params == null) { 116 | return index; 117 | } 118 | for (Object val : params) { 119 | if (val == null) { 120 | ps.setNull(index++, Types.INTEGER); 121 | } else { 122 | P3 setter = setter(val.getClass()); 123 | setter.e(ps, index++, val); 124 | } 125 | } 126 | return index; 127 | } 128 | static void psSet1(Object[] params, PreparedStatement ps) throws SQLException { 129 | psSet(params, ps, 1); 130 | } 131 | public static int execute(String query, Connection conn) { 132 | PreparedStatement ps = null; 133 | try { 134 | ps = conn.prepareStatement(query); 135 | return ps.executeUpdate(); 136 | } catch (SQLException e) { 137 | throw new RuntimeException(e); 138 | } finally { 139 | IOUtil.close(ps); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/qj/tool/sql/Template.java: -------------------------------------------------------------------------------- 1 | package qj.tool.sql; 2 | 3 | import java.lang.reflect.Type; 4 | import java.sql.Connection; 5 | import java.sql.PreparedStatement; 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.sql.Statement; 9 | import java.sql.Types; 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | import java.util.concurrent.atomic.AtomicReference; 15 | import java.util.regex.Matcher; 16 | 17 | import qj.util.Cols; 18 | import qj.util.IOUtil; 19 | import qj.util.ReflectUtil; 20 | import qj.util.RegexUtil; 21 | import qj.util.funct.F1; 22 | import qj.util.funct.F2; 23 | import qj.util.funct.Fs; 24 | import qj.util.funct.P1; 25 | import qj.util.funct.P3; 26 | 27 | @SuppressWarnings("UnusedDeclaration") 28 | public class Template { 29 | Class clazz; 30 | List> idFields; 31 | List> dataFields; 32 | String tableName; 33 | boolean autoIncrement = true; 34 | 35 | Template(Class clazz) { 36 | this.clazz = clazz; 37 | } 38 | 39 | 40 | static abstract class Field1 { 41 | F2 rsGet = null; 42 | P3 psSetter = null; 43 | String sqlName = null; 44 | Type type; 45 | abstract void setValue(Object val, M m); 46 | abstract Object getValue(M m); 47 | } 48 | 49 | public static Builder builder(Class clazz) { 50 | return new Builder<>(clazz); 51 | } 52 | 53 | public M insert(M m, Connection conn) { 54 | 55 | boolean hasId = getId(m) != null; 56 | List> fields = hasId ? allFields() : dataFields; 57 | 58 | PreparedStatement ps = null; 59 | ResultSet rs = null; 60 | try { 61 | String sql = "INSERT INTO `" + tableName + "`(" + fieldNames(fields) + ") VALUES(" + fieldsPH(fields) + ")"; 62 | // System.out.println(sql); 63 | ps = conn.prepareStatement(sql, Cols.isEmpty(idFields) ? Statement.NO_GENERATED_KEYS : Statement.RETURN_GENERATED_KEYS); // new String[] {"id"} 64 | psSet1(fields, m, ps); 65 | int result = ps.executeUpdate(); 66 | if (result != 1) { 67 | throw new RuntimeException("Failed to insert record into " + tableName + " table"); 68 | } 69 | 70 | if (!hasId && Cols.isNotEmpty(idFields)) { 71 | rs = ps.getGeneratedKeys(); 72 | if (rs.next()){ 73 | setId(m, rs.getLong(1)); 74 | } 75 | } 76 | 77 | return m; 78 | 79 | } catch (SQLException e) { 80 | throw new RuntimeException(e); 81 | } finally { 82 | IOUtil.close(rs); 83 | IOUtil.close(ps); 84 | } 85 | } 86 | 87 | 88 | public void insert(Collection col, Connection conn) { 89 | if (Cols.isEmpty(col)) { 90 | return; 91 | } 92 | 93 | List> fields = allFields(); 94 | 95 | PreparedStatement ps = null; 96 | try { 97 | String sql = "INSERT INTO `" + tableName + "`(" + fieldNames(fields) + ") VALUES(" + fieldsPH(fields) + ")"; 98 | // System.out.println(sql); 99 | ps = conn.prepareStatement(sql, Cols.isEmpty(idFields) ? Statement.NO_GENERATED_KEYS : Statement.RETURN_GENERATED_KEYS); // new String[] {"id"} 100 | 101 | for (M m : col) { 102 | psSet1(fields, m, ps); 103 | ps.addBatch(); 104 | } 105 | ps.executeBatch(); 106 | } catch (SQLException e) { 107 | throw new RuntimeException(e); 108 | } finally { 109 | IOUtil.close(ps); 110 | } 111 | // PreparedStatement ps = conn.prepareStatement(""); 112 | // ps.execute 113 | 114 | } 115 | 116 | 117 | private void setId(M m, long id) { 118 | Cols.getSingle(idFields).setValue(id, m); 119 | } 120 | public Object getId(M m) { 121 | Field1 idField = Cols.getSingle(idFields); 122 | return idField != null ? idField.getValue(m) : null; 123 | } 124 | 125 | 126 | private void psSet1(List> fields, M m, PreparedStatement ps) throws SQLException { 127 | int index = 1; 128 | for (Field1 field : fields) { 129 | Object val = field.getValue(m); 130 | 131 | if (val == null) { 132 | ps.setNull(index++, Types.INTEGER); 133 | } else { 134 | P3 setter = field.psSetter; 135 | setter.e(ps, index++, val); 136 | } 137 | } 138 | } 139 | 140 | private String fieldsPH(List> fields) { 141 | return Cols.join(Cols.createList(fields.size(), () -> "?"), ","); 142 | } 143 | 144 | private String fieldNames(List> fields) { 145 | return Cols.join((Iterable) Cols.yield(fields, (f) -> "`" + f.sqlName + "`"), ","); 146 | } 147 | 148 | public int delete(Connection conn, String cond, Object... params) { 149 | PreparedStatement ps = null; 150 | try { 151 | ps = conn.prepareStatement("DELETE FROM `" + tableName + "` " + (cond != null ? cond : "")); 152 | SQLUtil.psSet1(params, ps); 153 | 154 | return ps.executeUpdate(); 155 | 156 | } catch (SQLException e) { 157 | throw new RuntimeException(e); 158 | } finally { 159 | IOUtil.close(ps); 160 | } 161 | } 162 | 163 | /** 164 | * SET state=? WHERE id=? 165 | */ 166 | public int update(Connection conn, String query, Object... params) { 167 | PreparedStatement ps = null; 168 | try { 169 | ps = conn.prepareStatement("UPDATE `" + tableName + "` " + query); 170 | SQLUtil.psSet1(params, ps); 171 | 172 | return ps.executeUpdate(); 173 | 174 | } catch (SQLException e) { 175 | throw new RuntimeException(e); 176 | } finally { 177 | IOUtil.close(ps); 178 | } 179 | } 180 | 181 | public int update(M m, Connection conn, String cond, Object... params) { 182 | return update(m, allFields(), conn, cond, params); 183 | } 184 | 185 | public int update(M m, List> fields, Connection conn, String cond, Object... params) { 186 | PreparedStatement ps = null; 187 | try { 188 | ps = conn.prepareStatement("UPDATE `" + tableName + "` SET " + psSetUpdate(fields) + " " + cond); 189 | psSet1(fields, m, ps); 190 | SQLUtil.psSet(params, ps, fields.size() + 1); 191 | 192 | return ps.executeUpdate(); 193 | 194 | } catch (SQLException e) { 195 | throw new RuntimeException(e); 196 | } finally { 197 | IOUtil.close(ps); 198 | } 199 | } 200 | 201 | private String psSetUpdate(List> fields) { 202 | return Cols.join((Iterable)Cols.yield(fields, (f) -> "`" + f.sqlName + "`=?"), ","); 203 | } 204 | 205 | public M selectById(Object id, Connection conn) { 206 | if (id == null) { 207 | return null; 208 | } 209 | return select(conn, "WHERE `" + Cols.getSingle(idFields).sqlName + "`=?", id); 210 | } 211 | 212 | public M select(Connection conn, String query, Object... params) { 213 | Query parseQuery = parseSelectQuery(query); 214 | 215 | AtomicReference ret = new AtomicReference<>(); 216 | 217 | F1 f1 = Fs.f1(Fs.setter(ret), true); 218 | 219 | String cond = (parseQuery.cond != null ? parseQuery.cond : "") + " LIMIT 1"; 220 | 221 | each(f1, conn, parseQuery.fields, cond, params); 222 | return ret.get(); 223 | } 224 | 225 | public List selectAll(Connection conn) { 226 | List> fields = allFields(); 227 | String cond = ""; 228 | return selectList(conn, fields, cond, null); 229 | } 230 | 231 | private List selectList(Connection conn, List> fields, 232 | String cond, Object[] params) { 233 | LinkedList list = new LinkedList<>(); 234 | 235 | each(Fs.store(list), conn, fields, cond, params); 236 | 237 | return list; 238 | } 239 | 240 | private void each(P1 p1, Connection conn, List> fields, 241 | String cond, Object[] params) { 242 | each(Fs.f1(p1, false), conn, fields, cond, params); 243 | } 244 | 245 | private void each(F1 f1, Connection conn, List> fields, 246 | String cond, Object[] params) { 247 | PreparedStatement ps = null; 248 | ResultSet rs = null; 249 | String sql = "SELECT " + fieldNames(fields) + " FROM `" + tableName + "`" + (cond == null ? "" : " " + cond); 250 | try { 251 | ps = conn.prepareStatement(sql); 252 | SQLUtil.psSet1(params, ps); 253 | rs = ps.executeQuery(); 254 | 255 | while (rs.next()) { 256 | M m = ReflectUtil.newInstance(clazz); 257 | rsSet(fields, m, rs); 258 | if (f1.e(m)) { 259 | break; 260 | } 261 | } 262 | 263 | } catch (SQLException e) { 264 | throw new RuntimeException(e + ", sql=" + sql, e); 265 | } finally { 266 | IOUtil.close(rs); 267 | IOUtil.close(ps); 268 | } 269 | } 270 | 271 | private void rsSet(List> fields, M m, ResultSet rs) { 272 | int index = 1; 273 | for (Field1 field : fields) { 274 | field.setValue(field.rsGet.e(rs, index++), m); 275 | } 276 | } 277 | 278 | private List> allFields() { 279 | LinkedList> ret = new LinkedList<>(); 280 | ret.addAll(idFields); 281 | ret.addAll(dataFields); 282 | return ret; 283 | } 284 | 285 | public List selectList(Connection conn, String query, Object... params) { 286 | Query parseQuery = parseSelectQuery(query); 287 | 288 | return selectList(conn, parseQuery.fields, parseQuery.cond, params); 289 | } 290 | 291 | private Query parseSelectQuery(String query) { 292 | if (query == null) { 293 | return new Query<>(allFields(), null); 294 | } 295 | Matcher matcher = RegexUtil.matcher("^(?i)(?:SELECT (.+?) *)?(?:FROM .+? *)?((?:WHERE .+)?(?:ORDER BY .+)?)$", query); 296 | if (!matcher.matches()) { 297 | throw new RuntimeException("Can not parse this query: " + query); 298 | } 299 | 300 | return new Query<>(parseFields(matcher.group(1)), matcher.group(2)); 301 | } 302 | // 303 | // public static void main(String[] args) { 304 | // new Template<>(Aa.class).parseSelectQuery("SELECT receiver_name, receiver_address, receiver_phone WHERE order_by=? AND deliver_time > ? ORDER BY deliver_time DESC"); 305 | // } 306 | 307 | private List> parseFields(String fields) { 308 | if (fields == null) { 309 | return allFields(); 310 | } 311 | LinkedList> ret = new LinkedList<>(); 312 | for (String sqlName : fields.split("\\s*,\\s*")) { 313 | ret.add(getField(sqlName)); 314 | } 315 | return ret; 316 | } 317 | 318 | private Field1 getField(String sqlName) { 319 | for (Field1 field1 : idFields) { 320 | if (field1.sqlName.equals(sqlName)) { 321 | return field1; 322 | } 323 | } 324 | for (Field1 field1 : dataFields) { 325 | if (field1.sqlName.equals(sqlName)) { 326 | return field1; 327 | } 328 | } 329 | throw new RuntimeException("Can not find this field: " + sqlName); 330 | } 331 | 332 | static class Query { 333 | List> fields; 334 | String cond; 335 | 336 | public Query(List> fields, String cond) { 337 | this.fields = fields; 338 | this.cond = cond; 339 | } 340 | 341 | } 342 | 343 | public boolean exists(Connection conn, String cond, Object... params) { 344 | PreparedStatement ps = null; 345 | ResultSet rs = null; 346 | try { 347 | ps = conn.prepareStatement("SELECT 1 FROM `" + tableName + "` " + cond + " LIMIT 1"); 348 | SQLUtil.psSet1(params, ps); 349 | rs = ps.executeQuery(); 350 | 351 | return rs.next(); 352 | 353 | } catch (SQLException e) { 354 | throw new RuntimeException(e); 355 | } finally { 356 | IOUtil.close(rs); 357 | IOUtil.close(ps); 358 | } 359 | } 360 | 361 | public void save(M m, Connection conn) { 362 | Object id = getId(m); 363 | if (id != null) { 364 | int result = update(m, allFields(), conn, "WHERE " + Cols.getSingle(idFields).sqlName + "=?", id); 365 | if (result != 1) { 366 | throw new RuntimeException("Failed to update record into " + tableName + " table"); 367 | } 368 | 369 | } else { 370 | insert(m, conn); 371 | } 372 | } 373 | 374 | public void each(P1 p1, Connection conn) { 375 | each(p1, conn, null); 376 | } 377 | 378 | public void each(P1 p1, Connection conn, 379 | String query, Object... params) { 380 | Query parseQuery = parseSelectQuery(query); 381 | 382 | each(p1, conn, parseQuery.fields, parseQuery.cond, params); 383 | } 384 | 385 | public F1 selectByIdF(Connection conn) { 386 | return id -> selectById(id, conn); 387 | } 388 | 389 | public void update(M m, Connection conn) { 390 | int result = update(m, allFields(), conn, "WHERE " + Cols.getSingle(idFields).sqlName + "=?", getId(m)); 391 | if (result != 1) { 392 | throw new RuntimeException("Failed to update record into " + tableName + " table"); 393 | } 394 | } 395 | 396 | /** 397 | * 398 | * @param fields SET name, age 399 | */ 400 | public void update(M m, String fields, Connection conn) { 401 | List> fields1 = Cols.yield(Arrays.asList(fields.replaceFirst("(?i)^SET ","").split(",\\s*")), this::getField); 402 | int result = update(m, fields1, conn, "WHERE " + Cols.getSingle(idFields).sqlName + "=?", getId(m)); 403 | if (result != 1) { 404 | throw new RuntimeException("Failed to update record into " + tableName + " table"); 405 | } 406 | } 407 | 408 | public void delete(M m, Connection conn) { 409 | delete(conn, "WHERE " + Cols.getSingle(idFields).sqlName + "=?", getId(m)); 410 | } 411 | 412 | public void deleteById(Object id, Connection conn) { 413 | delete(conn, "WHERE " + Cols.getSingle(idFields).sqlName + "=?", id); 414 | } 415 | 416 | public void deleteAll(Connection conn) { 417 | delete(conn, null); 418 | } 419 | 420 | public String tableName() { 421 | return tableName; 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /src/main/java/qj/tool/web/ReloadingContext.java: -------------------------------------------------------------------------------- 1 | package qj.tool.web; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.LinkedList; 5 | 6 | import qj.util.ReflectUtil; 7 | import qj.util.funct.F0; 8 | import qj.util.funct.Fs; 9 | import qj.util.funct.P0; 10 | import qj.util.funct.P1; 11 | import qj.util.lang.DynamicClassLoader; 12 | 13 | @SuppressWarnings("UnusedDeclaration") 14 | public class ReloadingContext { 15 | 16 | protected String contextClassName; 17 | protected Object contextO; 18 | F0 classLoaderF; 19 | LinkedList> afterCreateContext = new LinkedList<>(); 20 | LinkedList> beforeCloseContext = new LinkedList<>(); 21 | 22 | @SuppressWarnings("UnusedDeclaration") 23 | public ReloadingContext(String contextClass, final String... classpaths) { 24 | this.contextClassName = contextClass; 25 | classLoaderF = () -> new DynamicClassLoader(classpaths); 26 | } 27 | 28 | public ReloadingContext(String contextClass, F0 classLoaderF) { 29 | this.contextClassName = contextClass; 30 | this.classLoaderF = classLoaderF; 31 | } 32 | 33 | public void reload() { 34 | close(); 35 | contextO = createContextObj(); 36 | Fs.invokeAll(afterCreateContext, contextO); 37 | } 38 | 39 | public void close() { 40 | if (contextO!= null) { 41 | Fs.invokeAll(beforeCloseContext, contextO); 42 | contextO = null; 43 | } 44 | } 45 | 46 | private Object createContextObj() { 47 | try { 48 | Class contextClass = classLoaderF.e().loadClass(contextClassName); 49 | return ReflectUtil.newInstance(contextClass); 50 | } catch (ClassNotFoundException e) { 51 | throw new RuntimeException(e); 52 | } 53 | } 54 | 55 | public void initWith(final String methodName, Object... params) { 56 | afterCreateContext.add(obj -> { 57 | ReflectUtil.invoke(methodName, obj, params); 58 | }); 59 | } 60 | public void beforeClose(final String methodName, Object... params) { 61 | beforeCloseContext.add(obj -> { 62 | ReflectUtil.invoke(methodName, obj, params); 63 | }); 64 | } 65 | @SuppressWarnings("UnusedDeclaration") 66 | public void setField(final String fieldName, Object value) { 67 | afterCreateContext.add(obj -> ReflectUtil.setFieldValue(value, fieldName, obj)); 68 | } 69 | 70 | public A get(String fieldName) { 71 | if (contextO == null) { 72 | reload(); 73 | } 74 | Field field = ReflectUtil.getField(fieldName, contextO.getClass()); 75 | return ReflectUtil.getFieldValue(field, contextO); 76 | } 77 | 78 | public void initWith(P0 afterCreateContext) { 79 | this.afterCreateContext.add(Fs.p1(afterCreateContext)); 80 | } 81 | 82 | public void beforeClose(P0 beforeClose) { 83 | this.beforeCloseContext.add(Fs.p1(beforeClose)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/qj/tool/web/ReloadingWebContext.java: -------------------------------------------------------------------------------- 1 | package qj.tool.web; 2 | 3 | import qj.util.funct.F0; 4 | import qj.util.funct.F1; 5 | import qj.util.funct.P0; 6 | 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServlet; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | 13 | @SuppressWarnings("UnusedDeclaration") 14 | public class ReloadingWebContext extends ReloadingContext { 15 | F1 shouldReload; 16 | 17 | @SuppressWarnings("UnusedDeclaration") 18 | public ReloadingWebContext(String contextClass, final String... classpaths) { 19 | super(contextClass, classpaths); 20 | 21 | shouldReload = req -> req.getMethod().equalsIgnoreCase("GET") && "true".equals(req.getParameter("rc")); 22 | } 23 | 24 | public ReloadingWebContext(String contextClass, F0 classLoaderF, F1 shouldReload) { 25 | super(contextClass, classLoaderF); 26 | this.shouldReload = shouldReload; 27 | } 28 | 29 | public HttpServlet stubServlet(final String servletName) { 30 | return new HttpServlet() { 31 | protected void service(HttpServletRequest req, 32 | HttpServletResponse resp) throws ServletException, 33 | IOException { 34 | 35 | if (shouldReload != null && shouldReload.e(req)) { 36 | reload(); 37 | } 38 | ((HttpServlet)get(servletName + "Servlet")).service(req, resp); 39 | } 40 | }; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/qj/tool/web/ResourceFilter.java: -------------------------------------------------------------------------------- 1 | package qj.tool.web; 2 | 3 | import qj.util.FileUtil; 4 | import qj.util.IOUtil; 5 | import qj.util.funct.F1; 6 | 7 | import javax.servlet.*; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.nio.charset.Charset; 13 | import java.util.LinkedList; 14 | import java.util.concurrent.TimeoutException; 15 | 16 | public class ResourceFilter implements Filter { 17 | 18 | private F1 rootRedirect; 19 | LinkedList> locationFs = new LinkedList<>(); 20 | 21 | public ResourceFilter(F1 rootRedirect, String... locations) { 22 | this.rootRedirect = rootRedirect; 23 | 24 | for (final String loc : locations) { 25 | if (loc != null) { 26 | locationFs.add(locF(loc)); 27 | } 28 | } 29 | } 30 | 31 | @Override 32 | public void destroy() { 33 | } 34 | 35 | @Override 36 | public void init(FilterConfig filterConfig) throws ServletException { 37 | } 38 | 39 | @Override 40 | public void doFilter(ServletRequest req1, ServletResponse resp1, 41 | FilterChain chain) throws IOException, ServletException { 42 | HttpServletRequest req = (HttpServletRequest) req1; 43 | HttpServletResponse resp = (HttpServletResponse) resp1; 44 | String uri = req.getRequestURI(); 45 | 46 | String redirectLocation; 47 | if (uri.equals("/") && (redirectLocation = rootRedirect.e(req)) != null) { 48 | resp.sendRedirect(redirectLocation); 49 | return; 50 | } 51 | 52 | File file = getFile(uri); 53 | if (file != null) { 54 | serve((HttpServletResponse) resp1, file, uri); 55 | } else if (uri.endsWith(".js") || uri.endsWith(".css") || uri.endsWith(".jpg") || uri.endsWith(".png")) { 56 | resp.sendError(404); 57 | } else { 58 | chain.doFilter(req1, resp1); 59 | } 60 | 61 | } 62 | 63 | private void serve(HttpServletResponse resp, File file, String uri) throws IOException { 64 | // resp.setCharacterEncoding("UTF-8"); 65 | 66 | resp.setContentType( 67 | uri.endsWith(".js" ) ? "application/javascript" : 68 | uri.endsWith(".css") ? "text/css" : 69 | uri.endsWith(".svg") ? "image/svg+xml" : 70 | uri.endsWith(".ttf") ? "application/octet-stream" : 71 | uri.endsWith(".woff") ? "font/woff" : 72 | null 73 | ); 74 | 75 | F1 contentFilter = getContentFilter(uri); 76 | if (contentFilter != null) { 77 | String content = contentFilter.e(IOUtil.toString(FileUtil.fileInputStream(file), "UTF-8")); 78 | resp.getOutputStream().write(content.getBytes(Charset.forName("UTF-8"))); 79 | } else { 80 | ServletOutputStream out = resp.getOutputStream(); 81 | try { 82 | IOUtil.connect(FileUtil.fileInputStream(file), out); 83 | } catch (IOException e) { 84 | if (e.getCause() != null && e.getCause() instanceof TimeoutException) { 85 | ; 86 | } else { 87 | throw e; 88 | } 89 | } 90 | } 91 | } 92 | 93 | LinkedList filters = new LinkedList<>(); 94 | @SuppressWarnings("UnusedDeclaration") 95 | public void addContentFilter(F1 uriCheck, F1 contentFilter) { 96 | filters.add(new ContentFilterHolder(uriCheck, contentFilter)); 97 | } 98 | public F1 getContentFilter(String uri) { 99 | for (ContentFilterHolder filter : filters) { 100 | if (filter.uriCheck.e(uri)) { 101 | return filter.contentFilter; 102 | } 103 | } 104 | return null; 105 | } 106 | 107 | public static F1 js = uri -> uri.endsWith(".js" ); 108 | static class ContentFilterHolder { 109 | F1 uriCheck; 110 | F1 contentFilter; 111 | public ContentFilterHolder( 112 | F1 uriCheck, 113 | F1 contentFilter 114 | ) { 115 | this.uriCheck = uriCheck; 116 | this.contentFilter = contentFilter; 117 | } 118 | } 119 | 120 | private File getFile(String uri) { 121 | if (uri.contains("//") || uri.contains("..") || uri.contains("./") || uri.contains("\\")) { 122 | return null; 123 | } 124 | 125 | for (F1 locF : locationFs) { 126 | File file = locF.e(uri); 127 | if (file != null) { 128 | return file; 129 | } 130 | } 131 | return null; 132 | } 133 | 134 | public F1 locF(final String loc) { 135 | return uri -> { 136 | File file = new File(loc + uri); 137 | if (file.exists() && file.isFile()) { 138 | return file; 139 | } 140 | return null; 141 | }; 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/qj/util/Cols.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import qj.util.funct.F0; 4 | import qj.util.funct.F1; 5 | 6 | import java.util.*; 7 | 8 | /** 9 | * Created by QuanLA 10 | * Date: Mar 2, 2006 11 | * Time: 9:10:49 AM 12 | */ 13 | @SuppressWarnings({"rawtypes","unchecked"}) 14 | public class Cols { 15 | 16 | /** 17 | * If a collection is empty 18 | * @param objs 19 | * @return 20 | */ 21 | public static boolean isEmpty(Collection objs) { 22 | return objs == null || objs.isEmpty(); 23 | } 24 | 25 | /** 26 | * If a collection is not empty 27 | * @param col 28 | * @return 29 | */ 30 | public static boolean isNotEmpty(Collection col) { 31 | return !isEmpty(col); 32 | } 33 | 34 | 35 | /** 36 | * Get single element, or null if Col is empty 37 | * @param collection 38 | * @return 39 | */ 40 | public static A getSingle( 41 | Collection collection) { 42 | if (collection == null) { 43 | return null; 44 | } 45 | for (A a : collection) { 46 | return a; 47 | } 48 | return null; 49 | } 50 | 51 | 52 | public static List createList(int size, F0 f) { 53 | ArrayList list = new ArrayList(size); 54 | for (int i = 0; i < size; i++) { 55 | list.add(f.e()); 56 | } 57 | return list; 58 | } 59 | 60 | 61 | /** 62 | * Create a string connecting all values in collection, separated with delimiter 63 | * @param objs 64 | * @param delimiter 65 | * @return 66 | */ 67 | public static String join(Iterable objs, String delimiter) { 68 | if (objs == null) { 69 | return ""; 70 | } 71 | StringBuilder sb = new StringBuilder(); 72 | for (A a : objs) { 73 | sb.append(a).append(delimiter); 74 | } 75 | if (sb.length() > 0) { 76 | sb.setLength(sb.length() - delimiter.length()); 77 | } 78 | return sb.toString(); 79 | } 80 | 81 | 82 | public static List yield(List col, F1 f1) { 83 | if (col!=null) { 84 | return yield(col, new ArrayList(col.size()), f1); 85 | } else { 86 | return null; 87 | } 88 | } 89 | 90 | /** 91 | * Apply function on every elements to get new collection of returned value 92 | * @param 93 | * @param 94 | * @param 95 | * @param inputs 96 | * @param col 97 | * @param f1 98 | * @return 99 | */ 100 | public static > C yield(Iterable inputs, C col, F1 f1) { 101 | // ArrayList list = new ArrayList(); 102 | if (inputs!=null) { 103 | for (A a : inputs) { 104 | T e = f1.e(a); 105 | if (e != null) { 106 | col.add(e); 107 | } 108 | } 109 | } 110 | return col; 111 | } 112 | 113 | /** 114 | * Create a map based on the Object... param. Each 2 values is an entry 115 | * which is a pair of key then value 116 | * @param objects The params that will be converted to map. 117 | * Format: [key1, value1, key2, value2] 118 | * @return The map after converted from param objects 119 | */ 120 | // @SuppressWarnings({"unchecked"}) 121 | public static Map map(Object... objects) { 122 | if (objects==null) { 123 | return null; 124 | } 125 | Map map = new LinkedHashMap(objects.length / 2); 126 | for (int i = 0; i < objects.length; i+=2) { 127 | map.put((A)objects[i], (B)objects[i + 1]); 128 | } 129 | return map; 130 | } 131 | } 132 | 133 | -------------------------------------------------------------------------------- /src/main/java/qj/util/FileUtil.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import qj.util.funct.F1; 4 | import qj.util.funct.F2; 5 | import qj.util.funct.Fs; 6 | import qj.util.funct.P2; 7 | 8 | import java.io.File; 9 | import java.io.FileInputStream; 10 | import java.io.FileNotFoundException; 11 | import java.io.IOException; 12 | import java.util.ArrayList; 13 | 14 | @SuppressWarnings("UnusedDeclaration") 15 | public class FileUtil { 16 | 17 | /** 18 | * 19 | * @param fileToRead 20 | * @return 21 | * @throws IOException 22 | */ 23 | public static byte[] readFileToBytes(File fileToRead) { 24 | try { 25 | return IOUtil.readData(new FileInputStream(fileToRead)); 26 | } catch (IOException e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | 31 | public static FileInputStream fileInputStream(File file) { 32 | try { 33 | return new FileInputStream(file); 34 | } catch (FileNotFoundException e) { 35 | throw new RuntimeException(e); 36 | } 37 | } 38 | public static FileInputStream fileInputStream(String file) { 39 | try { 40 | return new FileInputStream(file); 41 | } catch (FileNotFoundException e) { 42 | throw new RuntimeException(e); 43 | } 44 | } 45 | 46 | 47 | public static void eachFile(File path, P2 f) { 48 | eachFile(path, f, null); 49 | } 50 | 51 | public static void eachFile(File path, P2 f, F1 exclude) { 52 | eachFile(path, Fs.f2(f, true), exclude); 53 | } 54 | 55 | public static void eachFile(File path, F2 f, F1 exclude) { 56 | 57 | ArrayList relPath = new ArrayList<>(); 58 | 59 | if (path.isFile()) { 60 | f.e(path, Cols.join(relPath, File.separator)); 61 | } else { 62 | if (!eachFileInDir(path, f, relPath, exclude)) return; 63 | } 64 | } 65 | 66 | private static boolean eachFileInDir(File path, F2 f, ArrayList relPath, F1 exclude) { 67 | if (!path.exists() || !path.isDirectory()) { 68 | throw new RuntimeException("Invalid path: " + path); 69 | } 70 | for (File child : path.listFiles()) { 71 | if (exclude != null && exclude.e(child)) { 72 | // System.out.println("Excluded " + child); 73 | continue; 74 | } 75 | // System.out.println("Accepted " + child); 76 | 77 | if (child.isFile()) { 78 | if (!f.e(child, Cols.join(relPath, File.separator))) return false; 79 | } else { 80 | relPath.add(child.getName()); 81 | if (!eachFileInDir(child, f, relPath, exclude)) return false; 82 | relPath.remove(relPath.size() - 1); 83 | } 84 | } 85 | return true; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/qj/util/IOUtil.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import java.io.*; 4 | import java.sql.Connection; 5 | import java.sql.PreparedStatement; 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | 9 | /** 10 | * Created by Quan on 22/12/2014. 11 | */ 12 | public class IOUtil { 13 | 14 | /** 15 | * Read the stream into byte array 16 | * @param inputStream 17 | * @return 18 | * @throws java.io.IOException 19 | */ 20 | public static byte[] readData(InputStream inputStream) { 21 | try { 22 | return readDataNice(inputStream); 23 | } finally { 24 | close(inputStream); 25 | } 26 | } 27 | 28 | public static byte[] readDataNice(InputStream inputStream) { 29 | ByteArrayOutputStream boTemp = null; 30 | byte[] buffer = null; 31 | try { 32 | int read; 33 | buffer = new byte[8192]; 34 | boTemp = new ByteArrayOutputStream(); 35 | while ((read=inputStream.read(buffer, 0, 8192)) > -1) { 36 | boTemp.write(buffer, 0, read); 37 | } 38 | return boTemp.toByteArray(); 39 | } catch (IOException e) { 40 | throw new RuntimeException(e); 41 | } 42 | } 43 | 44 | 45 | /** 46 | * Close streams (in or out) 47 | * @param stream 48 | */ 49 | public static void close(Closeable stream) { 50 | if (stream != null) { 51 | try { 52 | if (stream instanceof Flushable) { 53 | ((Flushable)stream).flush(); 54 | } 55 | stream.close(); 56 | } catch (IOException e) { 57 | // When the stream is closed or interupted, can ignore this exception 58 | } 59 | } 60 | } 61 | 62 | public static void close(Connection conn) { 63 | if (conn != null) { 64 | try { 65 | conn.close(); 66 | } catch (SQLException e) { 67 | // When the conn is closed or interupted, can ignore this exception 68 | } 69 | } 70 | 71 | } 72 | 73 | public static void close(ResultSet rs) { 74 | if (rs != null) { 75 | try { 76 | rs.close(); 77 | } catch (SQLException e) { 78 | // When the file is closed already, can ignore this exception 79 | } 80 | } 81 | } 82 | public static void close(PreparedStatement ps) { 83 | if (ps != null) { 84 | try { 85 | ps.close(); 86 | } catch (SQLException e) { 87 | // When the file is closed already, can ignore this exception 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * Will close stream 94 | * @param in 95 | * @param charSet 96 | * @return 97 | */ 98 | public static String toString(InputStream in, String charSet) { 99 | return inputStreamToString_force(in, charSet); 100 | } 101 | 102 | /** 103 | * Will close stream 104 | * @param in 105 | * @param charSet 106 | * @return 107 | */ 108 | public static String inputStreamToString_force(InputStream in, String charSet) { 109 | try { 110 | return inputStreamToString(in, charSet); 111 | } catch (IOException e) { 112 | return null; 113 | } 114 | } 115 | 116 | /** 117 | * Reads in whole input stream and returns as a string
    118 | * Will close stream 119 | * @param in The input stream to read in, will be closed 120 | * by this method at finish 121 | * @param charSet charset to convert the input bytes into string 122 | * @return the result string 123 | * @throws IOException 124 | */ 125 | public static String inputStreamToString(InputStream in, String charSet) throws IOException { 126 | InputStreamReader inputStreamReader = null; 127 | try { 128 | inputStreamReader = charSet == null? new InputStreamReader(in) : new InputStreamReader(in, charSet); 129 | 130 | return toString(inputStreamReader); 131 | } catch (UnsupportedEncodingException e1) { 132 | throw new RuntimeException(e1); 133 | } finally { 134 | close(in); 135 | } 136 | } 137 | 138 | /** 139 | * Reads in whole input stream and returns as a string 140 | * @param reader The input reader to read in, will be closed 141 | * by this method at finish 142 | * @return the result string 143 | * @throws IOException 144 | */ 145 | public static String toString(Reader reader) { 146 | try { 147 | StringBuilder sb = new StringBuilder(); 148 | char[] buffer = new char[4096]; 149 | for (int read; (read = reader.read(buffer)) > -1;) { 150 | sb.append(buffer, 0, read); 151 | } 152 | return sb.toString(); 153 | } catch (IOException e) { 154 | throw new RuntimeException(e); 155 | } finally { 156 | close(reader); 157 | } 158 | } 159 | 160 | /** 161 | * Read the input stream and write to output stream 162 | * @param inputStream 163 | * @param out 164 | * @return 165 | * @throws IOException 166 | */ 167 | public static long connect(InputStream inputStream, OutputStream out) throws IOException { 168 | try { 169 | return dump(inputStream, out); 170 | } finally { 171 | close(inputStream); 172 | } 173 | } 174 | 175 | private static long dump(InputStream inputStream, OutputStream out) throws IOException { 176 | long total = 0; 177 | int read; 178 | int bufferSize = 8192; 179 | byte[] buffer = new byte[bufferSize]; 180 | while ((read=inputStream.read(buffer, 0, bufferSize)) > -1) { 181 | out.write(buffer, 0, read); 182 | total+=read; 183 | } 184 | out.flush(); 185 | return total; 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/qj/util/NameCaseUtil.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import java.util.regex.Matcher; 4 | 5 | import qj.util.funct.F1; 6 | 7 | public class NameCaseUtil { 8 | public static String camelToHyphen(String name) { 9 | return RegexUtil.replaceAll(name, "[A-Z]|[0-9]+", new F1() {public String e(Matcher m) { 10 | return (m.start() == 0 ? "" : "_") + m.group().toLowerCase(); 11 | }}); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/qj/util/ObjectUtil.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | /** 4 | * Created by QuanLA 5 | * Date: Apr 5, 2006 6 | * Time: 5:46:44 PM 7 | */ 8 | public class ObjectUtil { 9 | public static boolean equals(Object o1, Object o2) { 10 | return o1==null ? o2 == null : (o1 == o2 || o1.equals(o2)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/qj/util/PropertiesUtil.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import java.io.*; 4 | import java.util.Properties; 5 | 6 | public class PropertiesUtil { 7 | 8 | public static Properties loadPropertiesFromFile(String fileName) { 9 | return loadPropertiesFromFile(new File(fileName)); 10 | } 11 | 12 | /** 13 | * 14 | * @param file 15 | * @return 16 | */ 17 | public static Properties loadPropertiesFromFile(File file) { 18 | if (!file.exists()) { 19 | return null; 20 | } 21 | FileInputStream fis; 22 | try { 23 | fis = new FileInputStream(file); 24 | return load(fis); 25 | } catch (FileNotFoundException e) { 26 | throw new RuntimeException(e); 27 | } 28 | } 29 | 30 | 31 | /** 32 | * Load a Properties object from inputstream. Close the stream afterward 33 | * @param is 34 | * @return 35 | */ 36 | public static Properties load(InputStream is) { 37 | Properties properties = new Properties(); 38 | try { 39 | properties.load(is); 40 | } catch (IOException e) { 41 | throw new RuntimeException(e); 42 | } finally { 43 | try { 44 | is.close(); 45 | } catch (IOException e) { 46 | ; 47 | } 48 | } 49 | return properties; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/qj/util/ReflectUtil.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import qj.util.funct.F1; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | import java.util.*; 9 | import java.util.concurrent.atomic.AtomicReference; 10 | 11 | /** 12 | * Created by Quan on 22/12/2014. 13 | */ 14 | public class ReflectUtil { 15 | 16 | public static
    A newInstance(Class cla) { 17 | return (A) newInstance4(cla); 18 | } 19 | public static Object newInstance4(Class clazz) { 20 | 21 | try { 22 | return clazz.newInstance(); 23 | } catch (InstantiationException e) { 24 | Throwable cause = e.getCause(); 25 | if (cause==null) { 26 | cause = e; 27 | } 28 | throw new RuntimeException(cause); 29 | } catch (IllegalAccessException e) { 30 | throw new RuntimeException(e); 31 | } 32 | } 33 | 34 | 35 | public static Method getMethod(String methodName, Class clazz) { 36 | for (Method method : clazz.getMethods()) { 37 | if (method.getName().equals(methodName)) { 38 | return method; 39 | } 40 | } 41 | if (!clazz.equals(Object.class)) { 42 | Class superclass = clazz.getSuperclass(); 43 | if (superclass != null) { 44 | return getMethod(methodName, superclass); 45 | } else { 46 | return null; 47 | } 48 | } else { 49 | return null; 50 | } 51 | } 52 | 53 | public static Method getMethod(String methodName, Class[] paramClasses, Class clazz) { 54 | try { 55 | return clazz.getMethod(methodName, paramClasses); 56 | } catch (NoSuchMethodException e) { 57 | if (!clazz.equals(Object.class)) { 58 | Class superclass = clazz.getSuperclass(); 59 | if (superclass != null) { 60 | return getMethod(methodName, paramClasses, superclass); 61 | } 62 | return null; 63 | } else { 64 | return null; 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Invoke the method with given params 71 | * @param method 72 | * @param o 73 | * @param params 74 | * @return 75 | */ 76 | public static T invoke(Method method, Object o, Object... params) { 77 | try { 78 | return (T) method.invoke(o, params); 79 | } catch (IllegalAccessException e) { 80 | throw new RuntimeException(e); 81 | } catch (InvocationTargetException e) { 82 | throw new RuntimeException(e.getCause()); 83 | } 84 | } 85 | 86 | 87 | public static void setFieldValue(Object value, Field field, Object obj) { 88 | try { 89 | field.set(obj, value); 90 | } catch (IllegalAccessException e) { 91 | throw new RuntimeException(e); 92 | } 93 | } 94 | 95 | public static A getFieldValue(String field, Object obj) { 96 | return getFieldValue(getField(field, obj.getClass()), obj); 97 | } 98 | 99 | public static A getFieldValue(Field field, Object obj) { 100 | try { 101 | return (A) field.get(obj); 102 | } catch (IllegalAccessException e) { 103 | throw new RuntimeException(e); 104 | } 105 | } 106 | 107 | 108 | public static Field getField(String name, Class clazz) { 109 | try { 110 | return clazz.getDeclaredField(name); 111 | } catch (NoSuchFieldException e) { 112 | Class superClass = clazz.getSuperclass(); 113 | if (Object.class.equals(superClass)) { 114 | return null; 115 | } else { 116 | return getField(name, superClass); 117 | } 118 | } 119 | } 120 | 121 | public static Method findMethod(Class clazz, F1 f1) { 122 | AtomicReference m = new AtomicReference<>(); 123 | eachMethod(clazz, (Method obj) -> { 124 | if (f1.e(obj)) { 125 | m.set(obj); 126 | return true; 127 | } 128 | return false; 129 | }); 130 | return m.get(); 131 | } 132 | 133 | public static void eachMethod(Class clazz, F1 f1) { 134 | 135 | for (Method method : clazz.getMethods()) { 136 | if (f1.e(method)) { 137 | return; 138 | } 139 | } 140 | if (!clazz.equals(Object.class)) { 141 | Class superclass = clazz.getSuperclass(); 142 | if (superclass != null) { 143 | eachMethod(superclass, f1); 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * Invoke the method with given params 150 | */ 151 | public static Object invoke(String methodName, Object o, Object... params) { 152 | return invoke(getMethod(methodName, o.getClass()), o, params); 153 | } 154 | 155 | public static void setFieldValue(Object value, String field, Object obj) { 156 | try { 157 | setFieldValue(value, obj.getClass().getDeclaredField(field), obj); 158 | } catch (NoSuchFieldException e) { 159 | throw new RuntimeException(e); 160 | } 161 | } 162 | 163 | public static void invokeStatic(String methodName, Class clazz) { 164 | invoke(getMethod(methodName, clazz), null, null); 165 | } 166 | 167 | public static A getStaticFieldValue(String field, Class clazz) { 168 | try { 169 | return (A) getFieldValue(clazz.getDeclaredField(field), null); 170 | } catch (NoSuchFieldException e) { 171 | throw new RuntimeException(e); 172 | } 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/qj/util/RegexUtil.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import qj.util.funct.F1; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class RegexUtil { 11 | public static F1 compileF = new F1() { 12 | public Pattern e(Object obj) { 13 | return Pattern.compile(obj.toString()); 14 | } 15 | }; 16 | 17 | public static boolean matches(String key, String ptn) { 18 | return compileF.e(ptn).matcher(key).matches(); 19 | } 20 | 21 | 22 | public static Matcher matcher(String regex, String str) { 23 | return compileF.e(regex).matcher(str); 24 | } 25 | 26 | public static String replaceAll(String text,String ptn, F1 f1) { 27 | return StringChange.apply(replaceAll(f1, compileF.e(ptn), text), text); 28 | } 29 | 30 | public static List replaceAll( 31 | F1 f1, 32 | Pattern ptn, 33 | String text) { 34 | return replaceAll(f1, ptn, text, 0, -1); 35 | } 36 | 37 | public static List replaceAll( 38 | F1 f1, 39 | Pattern ptn, 40 | String text, 41 | int from, 42 | int to) { 43 | Matcher matcher = ptn.matcher(text); 44 | 45 | ArrayList changes = new ArrayList<>(); 46 | if (matcher.find(from)) { 47 | do { 48 | changes.add(StringChange.replace(matcher.start(), matcher.end(), f1.e(matcher))); 49 | } while (matcher.find() && (to==-1 || matcher.start() < to)); 50 | } 51 | return changes; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/qj/util/StringChange.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.LinkedList; 7 | 8 | import qj.util.math.Range; 9 | 10 | public abstract class StringChange implements Comparable { 11 | int priority = 0; 12 | public static StringChange replace(int start, int end, String replace) { 13 | return new ReplaceStringChange(start, end, replace); 14 | } 15 | public static StringChange replace(Range range, String replace) { 16 | return new ReplaceStringChange(range.getFrom(), range.getTo(), replace); 17 | } 18 | 19 | public abstract int pos(); 20 | 21 | public abstract void apply(StringBuilder sb); 22 | 23 | public int compareTo(StringChange o) { 24 | int ret = pos() - o.pos(); 25 | if (ret==0 26 | // && o instanceof InsertStringChange 27 | ) { 28 | if (this instanceof ReplaceStringChange) { 29 | return 1; 30 | } else { 31 | return -this.priority + o.priority; 32 | } 33 | 34 | } 35 | return ret; 36 | } 37 | 38 | public static class ReplaceStringChange extends StringChange { 39 | private final int start; 40 | private final int end; 41 | private final String replace; 42 | 43 | public ReplaceStringChange(int start, int end, String replace) { 44 | this.start = start; 45 | this.end = end; 46 | this.replace = replace; 47 | } 48 | 49 | public int pos() { 50 | return start; 51 | } 52 | 53 | public void apply(StringBuilder sb) { 54 | sb.replace(start, end, replace); 55 | } 56 | } 57 | public static class InsertStringChange extends StringChange { 58 | private final int pos; 59 | private final String value; 60 | 61 | public InsertStringChange(int pos, String value) { 62 | this.pos = pos; 63 | this.value = value; 64 | } 65 | public InsertStringChange(int pos, String value, int priority) { 66 | this.pos = pos; 67 | this.value = value; 68 | this.priority = priority; 69 | } 70 | 71 | public int pos() { 72 | return pos; 73 | } 74 | 75 | public void apply(StringBuilder sb) { 76 | sb.insert(pos, value); 77 | } 78 | } 79 | 80 | /** 81 | * @param replaces 82 | * @param text No need to sort first 83 | * @return 84 | */ 85 | public static String apply(Collection replaces, String text) { 86 | ArrayList list = new ArrayList(replaces); 87 | Collections.sort(list); 88 | StringBuilder sb = new StringBuilder(text); 89 | for (int i = list.size() - 1; i > -1; i--) { 90 | StringChange change = list.get(i); 91 | change.apply(sb); 92 | } 93 | return sb.toString(); 94 | } 95 | 96 | public static StringChange insert(String string, int pos, int priority) { 97 | return new InsertStringChange(pos, string, priority); 98 | } 99 | public static StringChange insert(String string, int pos) { 100 | return new InsertStringChange(pos, string); 101 | } 102 | 103 | public static StringChange delete(final Range selection) { 104 | return new StringChange() { 105 | public int pos() { 106 | return selection.getFrom(); 107 | } 108 | 109 | @Override 110 | public void apply(StringBuilder sb) { 111 | sb.replace(selection.getFrom(), selection.getTo(), ""); 112 | } 113 | }; 114 | } 115 | public static LinkedList replaceAll(String replaceFrom, String replaceTo, 116 | String to) { 117 | LinkedList ret = new LinkedList(); 118 | for (int indexOf=0; (indexOf = to.indexOf(replaceFrom, indexOf)) > -1;) { 119 | ret.add(replace(indexOf, indexOf + replaceFrom.length(), replaceTo)); 120 | indexOf+=replaceFrom.length(); 121 | } 122 | return ret; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/qj/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import qj.util.math.Range; 4 | 5 | /** 6 | * Created by Quan on 22/12/2014. 7 | */ 8 | public class StringUtil { 9 | 10 | /** 11 | * Trim the source and uppercase it's first character 12 | * 13 | * @param source - 14 | * The string to be malnipulated 15 | * @return The result String, null if the source String is null 16 | */ 17 | public static String upperCaseFirstChar(String source) { 18 | if (source == null) 19 | return null; 20 | 21 | source = source.trim(); 22 | 23 | if (source.length() == 0) 24 | return ""; 25 | else 26 | return Character.toUpperCase(source.charAt(0)) + source.substring(1, source.length()); 27 | } 28 | 29 | /** 30 | * 31 | * @return count 32 | */ 33 | public static int countHappens(char chr, CharSequence string) { 34 | if (string==null) { 35 | return 0; 36 | } 37 | int length = string.length(); 38 | int count = 0; 39 | for (int i = 0; i < length; i++) { 40 | if (string.charAt(i) == chr) 41 | count++; 42 | } 43 | return count; 44 | } 45 | 46 | public static boolean isEmpty(String str) { 47 | return str == null || str.length() == 0; 48 | } 49 | 50 | public static String replace(String replace, Range range, String text) { 51 | return text.substring(0, range.getFrom()) + replace + text.substring(range.getTo()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/qj/util/SystemUtil.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | import java.net.NetworkInterface; 8 | import java.net.SocketException; 9 | import java.util.*; 10 | import java.util.zip.ZipEntry; 11 | import java.util.zip.ZipException; 12 | import java.util.zip.ZipFile; 13 | 14 | import qj.util.funct.Fs; 15 | import qj.util.funct.P0; 16 | import qj.util.funct.P1; 17 | 18 | 19 | public class SystemUtil { 20 | static BufferedReader br; 21 | public static String readLine() { 22 | if (br==null) { 23 | br = new BufferedReader(new InputStreamReader(System.in)); 24 | } 25 | try { 26 | return br.readLine(); 27 | } catch (IOException e) { 28 | throw new RuntimeException(e); 29 | } 30 | } 31 | 32 | public static void onReturn(final P1 p1) { 33 | ThreadUtil.runStrong(new P0() { 34 | public void e() { 35 | while (true) { 36 | try { 37 | String readLine = readLine(); 38 | p1.e(readLine); 39 | } catch (Exception e1) { 40 | return; 41 | } 42 | } 43 | } 44 | }); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/qj/util/ThreadUtil.java: -------------------------------------------------------------------------------- 1 | package qj.util; 2 | 3 | import qj.util.funct.F0; 4 | import qj.util.funct.Fs; 5 | import qj.util.funct.P0; 6 | 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | 10 | public class ThreadUtil { 11 | 12 | private static final ExecutorService executorService = Executors.newCachedThreadPool(); 13 | public static void runStrong(final P0 p0) { 14 | executorService.execute(Fs.runnable(p0)); 15 | } 16 | 17 | 18 | public static ThreadLocalCache threadLocalCache(final F0 f) { 19 | 20 | final ThreadLocal threadLocal = new ThreadLocal(); 21 | 22 | ThreadLocalCache ret = new ThreadLocalCache(); 23 | 24 | ret.cacheF = new F0() {public A e() { 25 | A a = threadLocal.get(); 26 | if (a==null) { 27 | a = f.e(); 28 | threadLocal.set(a); 29 | } 30 | return a; 31 | }}; 32 | ret.removeF = new F0() {public A e() { 33 | A a = threadLocal.get(); 34 | threadLocal.set(null); 35 | return a; 36 | }}; 37 | 38 | return ret; 39 | } 40 | 41 | public static class ThreadLocalCache { 42 | public F0 cacheF; 43 | public F0 removeF; 44 | } 45 | 46 | /** 47 | * Sleep and wake on InterruptedException 48 | * @param timeToSleep in milliseconds 49 | */ 50 | public static void sleep(long timeToSleep) { 51 | if (timeToSleep <=0) 52 | return; 53 | try { 54 | Thread.sleep(timeToSleep); 55 | } catch (InterruptedException e) { 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/qj/util/funct/F0.java: -------------------------------------------------------------------------------- 1 | package qj.util.funct; 2 | 3 | 4 | /** 5 | * Represent a function that accept no parameter and return value 6 | * @param The return value 7 | */ 8 | public interface F0 { 9 | /** 10 | * Evaluate or execute the function 11 | * @return Result of execution 12 | */ 13 | T e(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/qj/util/funct/F1.java: -------------------------------------------------------------------------------- 1 | package qj.util.funct; 2 | 3 | /** 4 | * Represent a function that accept one parameter and return value 5 | * @param The only parameter 6 | * @param The return value 7 | */ 8 | public interface F1 { 9 | /** 10 | * Evaluate or execute the function 11 | * @param obj The parameter 12 | * @return Result of execution 13 | */ 14 | T e(A obj); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/qj/util/funct/F2.java: -------------------------------------------------------------------------------- 1 | package qj.util.funct; 2 | 3 | public interface F2{ 4 | T e(A a, B b); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/qj/util/funct/Fs.java: -------------------------------------------------------------------------------- 1 | package qj.util.funct; 2 | 3 | 4 | import java.util.Collection; 5 | import java.util.concurrent.atomic.AtomicReference; 6 | 7 | /** 8 | * The utility that employ idea of functional programming 9 | * 10 | */ 11 | @SuppressWarnings({"unchecked", "rawtypes"}) 12 | public class Fs extends FsGenerated { 13 | 14 | public static Runnable runnable(final P0 action) { 15 | return new Runnable() { 16 | public void run() { 17 | action.e(); 18 | } 19 | }; 20 | } 21 | 22 | /** 23 | * Just store the object to collection 24 | * @param col 25 | * @return 26 | */ 27 | public static P1 store(final Collection col) { 28 | return new P1() {public void e(A a) { 29 | col.add(a); 30 | }}; 31 | } 32 | 33 | public static P1 setter( 34 | final AtomicReference ref) { 35 | return new P1() {public void e(A obj) { 36 | ref.set(obj); 37 | }}; 38 | } 39 | 40 | public static void invokeAll(final Collection> col, A a) { 41 | for (P1 p1 : col) { 42 | p1.e(a); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/qj/util/funct/FsGenerated.java: -------------------------------------------------------------------------------- 1 | package qj.util.funct; 2 | 3 | public class FsGenerated { 4 | 5 | /** 6 | * Convert from a p0 to p1 7 | * @return p1 8 | */ 9 | public static P1 p1(final P0 p0) { 10 | return new P1(){public void e(final A a) { 11 | p0.e(); 12 | }}; 13 | } 14 | 15 | /** 16 | * Call to p and return fixed value 17 | * @param p1 the function to call before return value 18 | * @param ret the fixed value to return 19 | * @return ret 20 | */ 21 | public static F1 f1(final P1 p1, final R ret) { 22 | return new F1(){public R e(final A a) { 23 | p1.e(a); 24 | return ret; 25 | }}; 26 | } 27 | 28 | /** 29 | * Call to p and return fixed value 30 | * @param p2 the function to call before return value 31 | * @param ret the fixed value to return 32 | * @return ret 33 | */ 34 | public static F2 f2(final P2 p2, final R ret) { 35 | return new F2(){public R e(final A a, final B b) { 36 | p2.e(a, b); 37 | return ret; 38 | }}; 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/qj/util/funct/P0.java: -------------------------------------------------------------------------------- 1 | package qj.util.funct; 2 | 3 | public interface P0 { 4 | void e(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/qj/util/funct/P1.java: -------------------------------------------------------------------------------- 1 | package qj.util.funct; 2 | 3 | /** 4 | * Function that accept 1 objects and return nothing 5 | * @author QuanLA 6 | * 7 | * @param 8 | */ 9 | public interface P1 { 10 | void e(A obj); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/qj/util/funct/P2.java: -------------------------------------------------------------------------------- 1 | package qj.util.funct; 2 | 3 | /** 4 | * Function that accept 2 objects and return nothing 5 | * @author QuanLA 6 | * 7 | * @param 8 | * @param 9 | */ 10 | public interface P2 { 11 | void e(A a, B b); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/qj/util/funct/P3.java: -------------------------------------------------------------------------------- 1 | package qj.util.funct; 2 | 3 | public interface P3 { 4 | void e(A a, B b, C c); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/qj/util/lang/AggressiveClassLoader.java: -------------------------------------------------------------------------------- 1 | package qj.util.lang; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * Load all classes it can, leave the rest to the Parent ClassLoader 8 | */ 9 | public abstract class AggressiveClassLoader extends ClassLoader { 10 | 11 | Set loadedClasses = new HashSet<>(); 12 | Set unavaiClasses = new HashSet<>(); 13 | private ClassLoader parent = AggressiveClassLoader.class.getClassLoader(); 14 | 15 | @Override 16 | public Class loadClass(String name) throws ClassNotFoundException { 17 | if (loadedClasses.contains(name) || unavaiClasses.contains(name)) { 18 | return super.loadClass(name); // Use default CL cache 19 | } 20 | 21 | byte[] newClassData = loadNewClass(name); 22 | if (newClassData != null) { 23 | loadedClasses.add(name); 24 | return loadClass(newClassData, name); 25 | } else { 26 | unavaiClasses.add(name); 27 | return parent.loadClass(name); 28 | } 29 | } 30 | 31 | // public AggressiveClassLoader setParent(ClassLoader parent) { 32 | // this.parent = parent; 33 | // return this; 34 | // } 35 | 36 | /** 37 | * Handle exception 38 | * @param name 39 | * @return 40 | */ 41 | public Class load(String name) { 42 | try { 43 | return loadClass(name); 44 | } catch (ClassNotFoundException e) { 45 | throw new RuntimeException(e); 46 | } 47 | } 48 | 49 | protected abstract byte[] loadNewClass(String name); 50 | 51 | public Class loadClass(byte[] classData, String name) { 52 | Class clazz = defineClass(name, classData, 0, classData.length); 53 | if (clazz != null) { 54 | if (clazz.getPackage() == null) { 55 | definePackage(name.replaceAll("\\.\\w+$", ""), null, null, null, null, null, null, null); 56 | } 57 | resolveClass(clazz); 58 | } 59 | return clazz; 60 | } 61 | 62 | public static String toFilePath(String name) { 63 | return name.replaceAll("\\.", "/") + ".class"; 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/java/qj/util/lang/DynamicClassLoader.java: -------------------------------------------------------------------------------- 1 | package qj.util.lang; 2 | 3 | import qj.util.FileUtil; 4 | import qj.util.IOUtil; 5 | import qj.util.funct.F1; 6 | 7 | import java.io.ByteArrayInputStream; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.util.Collection; 12 | import java.util.LinkedList; 13 | import java.util.jar.JarFile; 14 | import java.util.zip.ZipEntry; 15 | 16 | public class DynamicClassLoader extends AggressiveClassLoader { 17 | LinkedList> loaders = new LinkedList<>(); 18 | 19 | public DynamicClassLoader(String... paths) { 20 | for (String path : paths) { 21 | File file = new File(path); 22 | 23 | F1 loader = loader(file); 24 | if (loader == null) { 25 | throw new RuntimeException("Path not exists " + path); 26 | } 27 | loaders.add(loader); 28 | } 29 | } 30 | 31 | @SuppressWarnings("UnusedDeclaration") 32 | public DynamicClassLoader(Collection paths) { 33 | for (File file : paths) { 34 | F1 loader = loader(file); 35 | if (loader == null) { 36 | throw new RuntimeException("Path not exists " + file.getPath()); 37 | } 38 | loaders.add(loader); 39 | } 40 | } 41 | 42 | 43 | public static F1 loader(File file) { 44 | if (!file.exists()) { 45 | return null; 46 | } else if (file.isDirectory()) { 47 | return dirLoader(file); 48 | } else { 49 | try { 50 | final JarFile jarFile = new JarFile(file); 51 | 52 | return jarLoader(jarFile); 53 | } catch (IOException e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | } 58 | 59 | private static File findFile(String filePath, File classPath) { 60 | File file = new File(classPath, filePath); 61 | return file.exists() ? file : null; 62 | } 63 | 64 | public static F1 dirLoader(final File dir) { 65 | return filePath -> { 66 | File file = findFile(filePath, dir); 67 | if (file == null) { 68 | return null; 69 | } 70 | 71 | return FileUtil.readFileToBytes(file); 72 | }; 73 | } 74 | 75 | private static F1 jarLoader(final JarFile jarFile) { 76 | return new F1() { 77 | public byte[] e(String filePath) { 78 | ZipEntry entry = jarFile.getJarEntry(filePath); 79 | if (entry == null) { 80 | return null; 81 | } 82 | try { 83 | return IOUtil.readData(jarFile.getInputStream(entry)); 84 | } catch (IOException e) { 85 | throw new RuntimeException(e); 86 | } 87 | } 88 | 89 | @Override 90 | protected void finalize() throws Throwable { 91 | IOUtil.close(jarFile); 92 | super.finalize(); 93 | } 94 | }; 95 | } 96 | 97 | @Override 98 | protected byte[] loadNewClass(String name) { 99 | // System.out.println("Loading class " + name); 100 | for (F1 loader : loaders) { 101 | byte[] data = loader.e(AggressiveClassLoader.toFilePath(name)); 102 | if (data!= null) { 103 | return data; 104 | } 105 | } 106 | return null; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/qj/util/lang/ExceptingClassLoader.java: -------------------------------------------------------------------------------- 1 | package qj.util.lang; 2 | 3 | import qj.util.funct.F1; 4 | 5 | /** 6 | * This class loader will not load certain classes, instead delegate to parent 7 | * class loader to do the job 8 | */ 9 | @SuppressWarnings("UnusedDeclaration") 10 | public class ExceptingClassLoader extends DynamicClassLoader { 11 | 12 | private F1 except; 13 | 14 | public ExceptingClassLoader(F1 except, String... paths) { 15 | super(paths); 16 | this.except = except; 17 | } 18 | 19 | @Override 20 | protected byte[] loadNewClass(String name) { 21 | if (except.e(name)) { 22 | return null; 23 | } 24 | 25 | return super.loadNewClass(name); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/qj/util/math/Range.java: -------------------------------------------------------------------------------- 1 | package qj.util.math; 2 | 3 | import qj.util.ObjectUtil; 4 | 5 | /** 6 | */ 7 | public class Range implements Comparable { 8 | private Integer from; 9 | private Integer to; 10 | 11 | public Range() { 12 | } 13 | 14 | public Range(Integer from, Integer to) { 15 | this.from = from; 16 | this.to = to; 17 | if (to != null && to < from) { 18 | throw new RuntimeException("to(" + to + ") < from(" + from + ")"); 19 | } 20 | } 21 | 22 | public Range(Long start, Long end) { 23 | this.from = start == null ? null : start.intValue(); 24 | this.to = end == null ? null : end.intValue(); 25 | if (to != null && to < from) { 26 | throw new RuntimeException(); 27 | } 28 | } 29 | 30 | public Integer getFrom() { 31 | return from; 32 | } 33 | 34 | public void setFrom(Integer from) { 35 | this.from = from; 36 | } 37 | 38 | public Integer getTo() { 39 | return to; 40 | } 41 | 42 | public void setTo(Integer to) { 43 | this.to = to; 44 | } 45 | 46 | public String toString() { 47 | return from + "-" + to; 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | final int prime = 31; 53 | int result = 1; 54 | result = prime * result + ((from == null) ? 0 : from.hashCode()); 55 | result = prime * result + ((to == null) ? 0 : to.hashCode()); 56 | return result; 57 | } 58 | 59 | @Override 60 | public boolean equals(Object obj) { 61 | if (this == obj) { 62 | return true; 63 | } 64 | if (!(obj instanceof Range)) { 65 | return false; 66 | } 67 | Range o2 = (Range) obj; 68 | return ObjectUtil.equals(from, o2.from) 69 | && ObjectUtil.equals(to, o2.to); 70 | } 71 | 72 | 73 | public int length() { 74 | return to - from; 75 | } 76 | public boolean isEmpty() { 77 | return to.equals(from); 78 | } 79 | 80 | public boolean isNotEmpty() { 81 | return !isEmpty(); 82 | } 83 | 84 | public int compareTo(Range o) { 85 | return from.compareTo(((Range)o).getFrom()); 86 | // return MathUtil.distance(this, o); 87 | } 88 | 89 | 90 | public static Range fromlength(int from, int length) { 91 | return new Range(from, from + length); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/kramdown/v1.kd: -------------------------------------------------------------------------------- 1 | For most Java developers out there, class reloading is an unfamiliar term, their usual code change routine was to restart the server and wait for the changes to be picked up. That is a very inefficient way of working that many others don't share. Techniques like Java Hot Swap, JRebel, DCEVM or fakereload have been around for some time and made people life much less miserable. I don't use any of them, for a long time I have developed my own way to save me from the annoying server restarts. And this is the right time for me to share my way of class reloading. 2 | 3 | If you are a Java guy, then most likely you would say that JVM don't reload classes, and all the above mentioned techniques are more of a hack than an official JVM feature. Well, you are correct... about the hacks. I would say that JVM has always been supporting class reloading. It's just that you may not know about it or may be not good enough to make use of it. 4 | 5 | The technique I'm about to discuss below is a very difficult one, it does classreloading but would require a deep understanding of Java classloading mechanism and a clear project structure mind. But to many senses this is the (only) right way to do class reloading. This is not a hack. 6 | 7 | In this post, I would spend some time to talk about the basics of Java classloading, and how JVM allow multiple class definitions to live together (in harmony). If you already know about classloading, I still beg you to read it, because it is interesting and very few document on the internet talk about it. Then I will make an example web application that make use of this technique to reload all classes with every page refresh (amazed huh, Ruby guys?) 8 | 9 | Example 1: Reloading 1 class 10 | ============= 11 | 12 | Please have a look at this: 13 | 14 | ~~~java 15 | package qj.blog.classreloading; 16 | 17 | import qj.util.ReflectUtil; 18 | import qj.util.lang.CompositeClassPathLoader; 19 | 20 | /** 21 | * Created by Quan on 26/10/2014. 22 | */ 23 | public class StaticInt { 24 | public static void main(String[] args) throws NoSuchFieldException { 25 | Class userClass = new CompositeClassPathLoader("target/classes").load("qj.blog.classreloading.StaticInt$User"); 26 | ReflectUtil.setFieldValue(11, userClass.getDeclaredField("age"), null); 27 | 28 | System.out.println("Seems to be the same class:"); 29 | System.out.println(userClass.getName()); 30 | System.out.println(User.class.getName()); 31 | System.out.println(); 32 | 33 | System.out.println("But why 2 different class loaders:"); 34 | System.out.println(userClass.getClassLoader()); 35 | System.out.println(User.class.getClassLoader()); 36 | System.out.println(); 37 | 38 | System.out.println("And different ages:"); 39 | System.out.println(User.age); 40 | System.out.println((Integer)ReflectUtil.getFieldValue(userClass.getDeclaredField("age"), null)); 41 | } 42 | 43 | public static class User { 44 | public static int age = 10; 45 | } 46 | } 47 | ~~~ 48 | And here is the output: 49 | 50 | ~~~ 51 | Seems to be the same class: 52 | qj.blog.classreloading.StaticInt$User 53 | qj.blog.classreloading.StaticInt$User 54 | 55 | But why there are 2 different class loaders: 56 | qj.util.lang.CompositeClassPathLoader@7530d0a 57 | sun.misc.Launcher$AppClassLoader@1f32e575 58 | 59 | And different ages: 60 | 10 61 | 11 62 | ~~~ 63 | ------------------ 64 | 65 | Ok, you said "classes are global" right? and there is always only one instance of 1 class object in 1 JVM, right? Then above example would definitely make you think again. 66 | 67 | << image with 2 class loaders on right col >> 68 | 69 | The example above have shown you that it's possible to have 2 or more class definitions living together in the same JVM, and we can pick any of them to use at will. Even better, they bring with them their own portfolio - the static members (age). 70 | 71 | Now it's time for you to clap your hands and show your gratitude toward the writers of our JVM, they made it with so clean a design that theoretically allow any thing to happen. Great, but then why class reloading has always been so hard? 72 | 73 | The reason is rooted from our JVM's biggest strength: reuse of resources. Classes are expected to be loaded only once and then be used forever because that makes our application fast. When a class link to another one, then JVM (classloader) just get the old definition and give it to him. This basically will makes a giant web of many classes linked together at runtime and they live happily forever after. Well, it's good for our JVM to be happy and fast, but not so good and happy for us developers. 74 | 75 | In order to correctly reload the classes, you will have to dump the whole Giant Web of Happy Classes altogether! 76 | Don't miss any link or some ghost classes will come back and haunt you :-< . 77 | 78 | That is a big requirement for any system architect, especially when we want to dump the classes but keep resources like DB connections. If you are thrilled, then don't use this technique. Seriously, don't use this technique! your team-mates will kill you, you boss will kill you and you customer will kill you too, they will curse you till death. But if you are patient enough to read this post thoroughly, then practice carefully, and apply real carefully to your project... then they will love you, and you will be their hero, the rock-star of the night. 79 | 80 | (( A side note: dumping the Giant Web... may sound like a big job for our processor and you may think that it would be as slow as restarting the server. But I assure you that it is really fast! and efficient. I can keep doing it the whole day long, reloading classes thousands of times without restarting the server, in my humble laptop )) 81 | 82 | Example 2: Simple context reloading 83 | ============== 84 | 85 | Okay, let's have a look at the next example. This time I will set up a simple context, with service and model and reload it together with classes every time we hit enter on the console: 86 | 87 | << >> 88 | 89 | As you can see, the main method will have a hold of the context object, and **that is the only link**. From that link, the context object will link to the context class, and service object to service class etc... With a clean design like this, we know exactly what to be refreshed in order to totally dump the old classes and objects. After the new context object is created and linked, the island of old context and classes (and classloader) will be isolated and is subjected to our merciless Garbage Collector (GC). 90 | 91 | A little explanation about why normally classes are so persistent and not get collected: it's because normally we load all our classes into the default classloader, and this classloader cache all the classes it has loaded, so as long as the classloader still connected to any alive thread, everyone (all loaded classes) will be immune to the Garbage Collector. In most running applications, we have very little reason to try isolate the default classloader and GC it ( why we try to reload String class, right? ). 92 | 93 | In this example, I also have demonstrated the class structure change. The user class in version 2 have added the new attribute name new getter for it. If you have used Java Hot Swap or understood it's limitation you will understand how difficult it is to achieve. Normally, JVM will not allow any class structure change to be Hot Swapped. JRebel guys is known to have invested a lot of effort into this, including tailoring the compiled class files and replace all new methods with reflection call. I can imagine how much work they made and how much trouble they been through... That's why their product price is so high, which is reasonable. But here it is, I'm giving it to you... for free, and better yet, it's not a hack, original classes are loaded and run. 94 | 95 | To this point, many may think that it's complicated to handle the isolated context object and classes, it may be too complicated to apply it in real world projects, and not many projects are so well designed that the **all mighty** context object can be picked up to be replaced so easily. Well, you are correct, if you are fine with server restarts or the crippled JVM Hot Swap then what we are doing is not worth the troubles, or if you are rich you can buy yourself a JRebel license, but I doubt other team members can afford it. Anyway, this approach is not for every one, not for every project, so most cases you will have to come up with your own solution for you project, and that would be an overwhelming task you if are not willing to do. What I provide here the general method, and the idea that it can work, and to apply to your project, you will have to go through many more troubles. But if you think that this is a good approach and may want to apply it to your projects, then you yourself or assign someone else (good enough) to deal with it, then the whole team will surely benefit. 96 | 97 | The reason why I have listed so many techniques in the beginning but only mentioned JVM Hot Swap and JRebel later on is because other options are obsolete and can not work the last time I check (with Java 8 ). As I said, class reloading is a difficult hack and without a good financial support they can not survive the JVM upgrades. 98 | 99 | Example 3: Reloading context while persisting connection object 100 | ============ 101 | 102 | In the next example I will show you how to save yourself a little more performance by keeping the old good connection object (and there may be many other things you want to keep through reloading phases ). 103 | 104 | If you are with me from beginning then you may see that this is not difficult at all, just like a decoration of example 2 105 | 106 | Other applications of this technique: 107 | ============ 108 | 109 | - Write logic classes that server can load on the fly (who need Groovy any more? ). 110 | - Write a custom server with developer mode that helps reload every thing. 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/main/kramdown/v3/content.md: -------------------------------------------------------------------------------- 1 | Java Class reloading tutorial 2 | ===== 3 | 4 | Java Class reloading has always been a very difficult technique, not only because very little document explaining this process, but also it require a very clear and careful system design. In this tutorial, I would like to provide a step by step explanation of this process and help you master this infamous technique. 5 | 6 | Work-space setup 7 | ------------ 8 | 9 | This tutorial require Maven, Git and either Eclipse or IntelliJ IDEA. 10 | 11 | All source code for this tutorial is uploaded at this GitHub: 12 | [https://github.com/quanla/classreloading](https://github.com/quanla/classreloading) 13 | 14 | ### If you are using Eclipse: 15 | - Run command: "`mvn eclipse:eclipse`" to generate Eclipse's project files 16 | - Load the generated project. 17 | - Set output path to "`target/classes`" 18 | 19 | ### If you are using IntelliJ: 20 | 21 | - Import the project's pom file. 22 | - IntelliJ will not auto-compile when you are running any example, so you have to choose either: 23 | - Run the examples inside IntelliJ then you have to compile the class manually with (`Alt+B E`) 24 | - Run the examples outside IntelliJ with the run_example*.bat 25 | 26 | 27 | Example 1: StaticInt 28 | ------------- 29 | 30 | Source code: `src/main/java/qj/blog/classreloading/example1/StaticInt.java` 31 | 32 | The first example will give you a general understanding of Class Loader: 33 | 34 | ~~~ java 35 | 36 | Class userClass1 = User.class; 37 | Class userClass2 = new DynamicClassLoader("target/classes") 38 | .load("qj.blog.classreloading.example1.StaticInt$User"); 39 | 40 | ~~~ 41 | 42 | In this example, there will be 2 User classes loaded into the memory. The `userClass1` will be loaded by the JVM's default class loader, the second one using the `DynamicClassLoader`, a custom class loader whose source code also provided in the **GitHub** project. 43 | 44 | 45 | Here is the rest of the source code: 46 | 47 | ~~~ 48 | 49 | out.println("Seems to be the same class:"); 50 | out.println(userClass1.getName()); 51 | out.println(userClass2.getName()); 52 | out.println(); 53 | 54 | out.println("But why there are 2 different class loaders:"); 55 | out.println(userClass1.getClassLoader()); 56 | out.println(userClass2.getClassLoader()); 57 | out.println(); 58 | 59 | User.age = 11; 60 | out.println("And different age values:"); 61 | out.println((int) ReflectUtil.getStaticFieldValue("age", userClass1)); 62 | out.println((int) ReflectUtil.getStaticFieldValue("age", userClass2)); 63 | 64 | ~~~ 65 | 66 | And the output: 67 | 68 | ~~~ 69 | 70 | Seems to be the same class: 71 | qj.blog.classreloading.example1.StaticInt$User 72 | qj.blog.classreloading.example1.StaticInt$User 73 | 74 | But why there are 2 different class loaders: 75 | qj.util.lang.DynamicClassLoader@3941a79c 76 | sun.misc.Launcher$AppClassLoader@1f32e575 77 | 78 | And different age values: 79 | 11 80 | 10 81 | 82 | ~~~ 83 | 84 | So as you can see here, although the `User` classes have same name, they are actually 2 different classes, and they can be managed, manipulated independently. The age value, although declared as static, exists in 2 versions, attaching to each classes and can be changed independently too. 85 | 86 | << image `1_2_loaders.jpg` >> 87 | 88 | A word about the `ClassLoader`: they are the portal bringing classes into the JVM. When one class require another class to be loaded, it's the ClassLoader's task to do the loading. 89 | 90 | In this example, the custom `ClassLoader` named `DynamicClassLoader` is used to load the second version of User class. If instead of `DynamicClassLoader`, we use the default class loader ( with command: `StaticInt.class.getClassLoader()` ) then the same User class will be used, as all loaded classes are cached. 91 | 92 | #### The DynamicClassLoader 93 | Unlike the default behaviour of a ClassLoaders, our `DynamicClassLoader` has a more aggressive strategy. A normal class loader would give its parent `ClassLoader` the priority and only load classes that its parent can not load. That is suitable for normal circumstances, but not in our case. The `DynamicClassLoader` on the other hand will try to look through all its class paths and resolve the target class before it give up the right to its parent. 94 | 95 | Few more things about this Dynamic Class Loader: 96 | - It's capable of loading all system classes and library - if you specify in it's classpaths. 97 | - The loaded classes have the same performance and other attributes as other classes loaded by the default class loader. 98 | - The Dynamic Class Loader can be Garbage Collected together with all of it's loaded classes and objects. 99 | 100 | In this example 1, the Dynamic ClassLoader is created with only 1 class path: "target/classes" ( in our current directory ), so it's capable of loading all the classes reside in that classpath. For all the classes not in there, it will have to refer to the parent class loader. For example we need to load String class in our StaticInt class, and our class loader does not have access to the rt.jar in our jre folder, so the String class of the parent class loader will be used. 101 | 102 | With the ability to load and use 2 versions of the same class, we are now thinking of dumping the old version and load the new one to replace it. In the next example, we will do that... continuously. 103 | 104 | 105 | Example 2: ReloadingContinuously 106 | ================ 107 | 108 | Source code: `src/main/java/qj/blog/classreloading/example2/ReloadingContinuously.java` 109 | 110 | This example will show you that the JRE can load and reload classes forever, with old classes dumped and garbage collected, and brand new class loaded from hard drive and put to use. 111 | 112 | ~~~ java 113 | 114 | Class userClass = new DynamicClassLoader("target/classes") 115 | .load("qj.blog.classreloading.example2.ReloadingContinuously$User"); 116 | ReflectUtil.invokeStatic("hobby", userClass); 117 | ThreadUtil.sleep(2000); 118 | 119 | ~~~ 120 | 121 | With every 2 seconds, the old `User` class will be dumped, a new one will be loaded and method `hobby` is invoked. 122 | 123 | Here is the `User` class definition: 124 | 125 | ~~~ java 126 | @SuppressWarnings("UnusedDeclaration") 127 | public static class User { 128 | public static void hobby() { 129 | playFootball(); // Will comment later 130 | // playBasketball(); // Will uncomment later 131 | } 132 | 133 | // Will comment later 134 | public static void playFootball() { 135 | System.out.println("Play Football"); 136 | } 137 | 138 | // Will uncomment later 139 | // public static void playBasketball() { 140 | // System.out.println("Play Basketball"); 141 | // } 142 | } 143 | ~~~ 144 | 145 | When running this application, you should try to comment and uncomment the code in User class, you will see that the newest definition will always be used. 146 | 147 | Here is the example output: 148 | 149 | ~~~ 150 | ... 151 | Play Football 152 | Play Football 153 | Play Football 154 | Play Basketball 155 | Play Basketball 156 | Play Basketball 157 | 158 | ~~~ 159 | 160 | Every time a new instance of `DynamicClassLoader` is created, and it will load the `User` class in the "`target/classes`" folder which we had set Eclipse or IntelliJ to output the latest class file. All old `DynamicClassLoader`s and old `User` classes will be unlinked and subjected to be garbage-collected. 161 | 162 | << Image `2_many_loaders.jpg` >> 163 | 164 | If you are familiar with JVM HotSpot, then it's noteworthy here that the class structure can also be changed and reloaded: playFootball method is to be removed and playBasketball method is to be added. This is different to HotSpot, which allow only method content be changed, or the class can not be reloaded. 165 | 166 | Now that we are capable of reloading a class, it is time to try reloading many classes at once. Let's try it out in the next example. 167 | 168 | 169 | Example 3: ContextReloading 170 | ================ 171 | 172 | Source code: `src/main/java/qj/blog/classreloading/example3/ContextReloading.java` 173 | 174 | This example's source code is rather large, so I only put here parts of it, for the actual source code file, please refer to the GitHub project. 175 | 176 | The `main` method: 177 | 178 | ~~~ java 179 | 180 | for (;;) { 181 | Object context = createContext(); 182 | invokeHobbyService(context); 183 | ThreadUtil.sleep(2000); 184 | } 185 | 186 | ~~~ 187 | 188 | Method `createContext`: 189 | 190 | ~~~ java 191 | Class contextClass = new DynamicClassLoader("target/classes") 192 | .load("qj.blog.classreloading.example3.ContextReloading$Context"); 193 | Object context = newInstance(contextClass); 194 | invoke("init", context); 195 | return context; 196 | 197 | ~~~ 198 | 199 | Method `invokeHobbyService`: 200 | 201 | ~~~ java 202 | 203 | Object hobbyService = getFieldValue("hobbyService", context); 204 | invoke("hobby", hobbyService); 205 | ~~~ 206 | 207 | And here is the `Context` class: 208 | 209 | ~~~ java 210 | public static class Context { 211 | public HobbyService hobbyService = new HobbyService(); 212 | 213 | public void init() { 214 | // Init your services here 215 | hobbyService.user = new User(); 216 | } 217 | } 218 | ~~~ 219 | 220 | 221 | The `Context` class in this example is much more complicated than the `User` class in the previous examples: it has link to other classes, it has the `init` method to be called every it is instantiated. Basically, it's very similar to real world application's context classes. So being able to reload this `Context` class together with all it's linked classes is a great step toward applying this technique to real life. 222 | 223 | 224 | << image `3_context_reloading.jpg` >> 225 | 226 | 227 | As the number of classes and objects grow, our step of "drop old versions" will also be more complicated, this is also the biggest reason why class reloading is so difficult. To possibly drop old versions we will have to make sure that once the new context created all the references to the old classes and objects are dropped. Soon I will show you the way to deal with it elegantly: 228 | 229 | The `main` method here will have a hold of the context object, and **that is the only link** to all the things that need to be dropped. If we break that link, the context object and the context class, and the service object ... will all be subjected to the `Garbage Collector`. 230 | 231 | A little explanation about why normally classes are so persistent and not get Garbage Collected: 232 | 233 | - Normally we load all our classes into the default classloader 234 | - The class - class loader is a 2-ways relationship with the classloader also cache all the classes it has loaded 235 | - So as long as the classloader still connected to any alive thread, everything (all loaded classes) will be immune to the Garbage Collector. 236 | - There is a little chance that the default classloader is not garbage collected, we can, but it will require stopping all current threads launch anew inside new DefaultClassLoader's scope ( Luckily we don't have to reload the Thread class itself because all those basic classes are loaded and cached by a special JVM classloader ) 237 | 238 | With this example, we see that reloading all application's classes is actually rather easy, but what if we want some objects (and their classes) not to be reloaded and be reused between reloading cycles? Let's look at the next example. 239 | 240 | 241 | Example 4: KeepConnectionPool 242 | ================ 243 | 244 | Source code: `src/main/java/qj/blog/classreloading/example4/KeepConnectionPool.java` 245 | 246 | The `main` method: 247 | 248 | ~~~ java 249 | 250 | ConnectionPool pool = new ConnectionPool(); 251 | 252 | for (;;) { 253 | Object context = createContext(pool); 254 | 255 | invokeService(context); 256 | 257 | ThreadUtil.sleep(2000); 258 | } 259 | 260 | ~~~ 261 | 262 | So you can see that the trick here is loading the `ConnectionPool` class and instantiate it outside the reloading cycle, keeping it in the persisted space, and pass the reference to the `Context` objects 263 | 264 | The `createContext` method is also a little bit different: 265 | 266 | ~~~ java 267 | 268 | ExceptingClassLoader classLoader = new ExceptingClassLoader( 269 | (className) -> className.contains("$Connection"), 270 | "target/classes"); 271 | Class contextClass = classLoader.load(KeepConnectionPool.class.getName() + "$Context"); 272 | Object context = newInstance(contextClass); 273 | 274 | setFieldValue(pool, "pool", context); 275 | invoke("init", context); 276 | 277 | return context; 278 | 279 | ~~~ 280 | 281 | From now on, we will call the objects and classes that are reloaded with every cycle the "Reloadable space" and others the "Persisted space", which contain the objects and classes not recycled and not renewed during the reloading cycles. We will have to be very clear about which object or class stay in which space, thus drawing a separation line between these 2 spaces. 282 | 283 | << Image `4_persisting_connection.jpg` >> 284 | 285 | As seen from the picture, not only the Context Object and the UserService Object is referring to the ConnectionPool Object, but the Context and UserService classes are also referring to the ConnectionPool class. This is a very dangerous situation which often lead to confusion and failure. The ConnectionPool class must not be loaded by our `DynamicClassLoader`, there must be only one `ConnectionPool` class in the memory, which is the one loaded by the default `ClassLoader`. 286 | 287 | What if our `DynamicClassLoader` accidentally load the `ConnectionPool` class? Then the `ConnectionPool` object from the persisted space can not be passed to the Context object, because the Context object is expecting an object of a different class, which named `ConnectionPool` too, but is actually a different class. 288 | 289 | So how do we prevent our `DynamicClassLoader` from loading the `ConnectionPool` class? Instead of using `DynamicClassLoader`, this example use a subclass of it named: `ExceptingClassLoader`, which will pass the loading to super classloader base on a condition function: 290 | 291 | ~~~ java 292 | (className) -> className.contains("$Connection") 293 | ~~~ 294 | 295 | If we don't use `ExceptingClassLoader` here, then the `DynamicClassLoader` would load the `ConnectionPool` class because that class reside in the "`target/classes`" folder. Another way to prevent the `ConnectionPool` class to be picked up by our `DynamicClassLoader` is to compile the `ConnectionPool` class to a different folder, maybe put to a different module and it will be compiled separately. 296 | 297 | #### Rules for choosing space 298 | 299 | Now, the job gets really confusing, how to determine which classes would be in Persisted space, and which classes in Reloadable space. Here are the rules: 300 | 301 | - 1 class can serve in either or both Persisted space and Reloadable space if objects of its type are not sent across the 2 spaces. For example: utility classes with all static methods like `StringUtils` 302 | - 1 class has objects sent across the 2 spaces must be in Persisted space - like the `ConnectionPool` class. All linked classes must also be in Persisted space - like the `Connection` class which is linked from `ConnectionPool`. 303 | 304 | So you can see that the rules are not very restricted, except for the crossing classes that has objects transfer across the 2 spaces, all other classes can be freely used in either Persisted space or Reloadable space or both. Of course only classes in Reloadable space will enjoy being reloaded with reloading cycles. 305 | 306 | So the most challenging problem with Class Reloading is dealt with. In the next example, we will try to apply this technique to a simple Web Application, and enjoy the reloading Java classes just like any scripting language 307 | 308 | Example 5: Little Phone Book 309 | ================ 310 | 311 | Source code: `src/main/java/qj/blog/classreloading/example5/LittlePhoneBookMain.java` 312 | 313 | This example will be very similar to what a normal web application should look like. It is a Single Page Application with AngularJS, db is SQLite, Maven, and Jetty Embedded Web Server. 314 | 315 | Here is the reloadable space in the web server's structure: 316 | 317 | << Image `5_web_app.jpg` >> 318 | 319 | The web server will not hold reference to the real servlets (which must stay in the reloadable space, so to be reloaded ), what it holds is stub servlets, which with every call to its service method, will resolve the actual servlet in the actual context to run. 320 | 321 | This example also introduce a new object `ReloadingWebContext`, which provide to the Web Server all values like a normal Context, but internally hold reference to a actual context object that can be reloaded by a `DynamicClassLoader`. It is this `ReloadingWebContext` which provide stub servlets to the web server. 322 | 323 | << Image `5_reloading_web_context.jpg` >> 324 | 325 | The `ReloadingWebContext` will be the wrapper of the actual context, which will: 326 | 327 | - Reload the actual context when a HTTP GET to "/" in called. 328 | - Provide Stub servlet to web server. 329 | - Set values, invoke methods every time the actual context is initialized or destroyed. 330 | - Can be configured to reload context or not, and which ClassLoader is used for reloading. This will help when running the application in Production. 331 | 332 | Because it's very important to understanding how we isolate the Persisted space and Reloadable space, here are the 2 classes that are crossing between the 2 spaces: 333 | 334 | - Class `qj.util.funct.F0` for object `public F0 connF` in `Context`: Function object, will return a Connection each time the function is invoked. This class resides in the qj.util package, which is excluded from the `DynamicClassLoader`. 335 | - Class `java.sql.Connection` for object `public F0 connF` in `Context`: Normal sql connection object. This class does not reside in our `DynamicClassLoader`'s class path so it won't be picked up. 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | -------------------------------------------------------------------------------- /src/main/kramdown/v3/outline.md: -------------------------------------------------------------------------------- 1 | - Workspace setup guide for both Eclipse and IntelliJ 2 | - Ex 1: StaticInt.java : Ice breaking, eye opening. Making the readers see that Class Reloading is possible, and Classes are not "GLOBAL" and SINGLETON as most of them may think. 3 | - Ex 2: ReloadingContinuously.java : Demonstrate the dynamic class loader to pick up class changes on the fly. This is what every one looking for. 4 | - Ex 3: ContextReloading.java : Give them more confidence, reloading the whole application context. Giving advice on isolating every things that are reloaded. 5 | - Ex 4: KeepConnectionPool.java : Visualize the separation line between what is persisted and what is reloaded, and the connections between the 2 worlds. 6 | - Ex 5: WebApp.java : Nearest to real world application. 7 | Things that I did setup with class reloading -------------------------------------------------------------------------------- /src/main/kramdown/v4/content.md: -------------------------------------------------------------------------------- 1 | Java Class reloading tutorial 2 | ===== 3 | 4 | Java Class reloading has always been a very difficult technique, not only because very little document explaining this process, but also it require a very clear and careful system design. In this tutorial, I would like to provide a step by step explanation of this process and help you master this infamous technique. 5 | 6 | Work-space setup 7 | ------------ 8 | 9 | This tutorial require Maven, Git and either Eclipse or IntelliJ IDEA. 10 | 11 | All source code for this tutorial is uploaded at this GitHub: 12 | [https://github.com/quanla/classreloading](https://github.com/quanla/classreloading). Please check it out to your local hard drive, we will need to set up a proper workspace. 13 | 14 | ### If you are using Eclipse: 15 | - Run command: `mvn eclipse:eclipse` to generate Eclipse's project files 16 | - Load the generated project. 17 | - Set output path to `target/classes` 18 | 19 | ### If you are using IntelliJ: 20 | 21 | - Import the project's pom file. 22 | - IntelliJ will not auto-compile when you are running any example inside it, so you have to choose either: 23 | - Run the examples inside IntelliJ, then every time you want to compile, you'll have to press `Alt+B E` 24 | - Run the examples outside IntelliJ with the run_example*.bat; Set IntelliJ compiler's auto-compile to true then every time you change any java file, IntelliJ will auto-compile it. 25 | 26 | 27 | Example 1: StaticInt 28 | ------------- 29 | 30 | Source code: `src/main/java/qj/blog/classreloading/example1/StaticInt.java` 31 | 32 | The first example will give you a general understanding of Class Loader: 33 | 34 | ~~~ java 35 | 36 | Class userClass1 = User.class; 37 | Class userClass2 = new DynamicClassLoader("target/classes") 38 | .load("qj.blog.classreloading.example1.StaticInt$User"); 39 | 40 | ~~~ 41 | 42 | In this example, there will be 2 User classes loaded into the memory. The `userClass1` will be loaded by the JVM's default class loader, the second one using the `DynamicClassLoader`, a custom class loader whose source code also provided in the **GitHub** project. 43 | 44 | 45 | Here is the rest of the source code: 46 | 47 | ~~~ 48 | 49 | out.println("Seems to be the same class:"); 50 | out.println(userClass1.getName()); 51 | out.println(userClass2.getName()); 52 | out.println(); 53 | 54 | out.println("But why there are 2 different class loaders:"); 55 | out.println(userClass1.getClassLoader()); 56 | out.println(userClass2.getClassLoader()); 57 | out.println(); 58 | 59 | User.age = 11; 60 | out.println("And different age values:"); 61 | out.println((int) ReflectUtil.getStaticFieldValue("age", userClass1)); 62 | out.println((int) ReflectUtil.getStaticFieldValue("age", userClass2)); 63 | 64 | ~~~ 65 | 66 | And the output: 67 | 68 | ~~~ 69 | 70 | Seems to be the same class: 71 | qj.blog.classreloading.example1.StaticInt$User 72 | qj.blog.classreloading.example1.StaticInt$User 73 | 74 | But why there are 2 different class loaders: 75 | qj.util.lang.DynamicClassLoader@3941a79c 76 | sun.misc.Launcher$AppClassLoader@1f32e575 77 | 78 | And different age values: 79 | 11 80 | 10 81 | 82 | ~~~ 83 | 84 | So as you can see here, although the `User` classes have same name, they are actually 2 different classes, and they can be managed, manipulated independently. The age value, although declared as static, exists in 2 versions, attaching to each classes and can be changed independently too. 85 | 86 | << image `1_2_loaders.jpg` >> 87 | 88 | A word about the `ClassLoader`: they are the portal bringing classes into the JVM. When one class require another class to be loaded, it's the ClassLoader's task to do the loading. 89 | 90 | In this example, the custom `ClassLoader` named `DynamicClassLoader` is used to load the second version of User class. If instead of `DynamicClassLoader`, we use the default class loader ( with command: `StaticInt.class.getClassLoader()` ) then the same User class will be used, as all loaded classes are cached. 91 | 92 | #### The DynamicClassLoader 93 | 94 | The `DynamicClassLoader` is in a sense the most important part of this article, so we must understand how it works before making anything possible. 95 | 96 | First of all, there can be multiple class loaders in a normal Java program. The one that loads your main class is the default one, and from your code, you can create and use as many more class loaders as you like to. 97 | 98 | About loading behavior: A default/normal class loader would give its parent `ClassLoader` the priority and only load classes that its parent can not load. That is suitable for normal circumstances, but not in our case. Unlike the default behavior of a ClassLoaders, our `DynamicClassLoader` has a more aggressive strategy. The `DynamicClassLoader` on the other hand will try to look through all its class paths and resolve the target class before it give up the right to its parent. 99 | 100 | This is the code snippet in the `AggressiveClassLoader`, the parent class of `DynamicClassLoader`, which tells why it's **aggressive**: 101 | 102 | ~~~ java 103 | byte[] newClassData = loadNewClass(name); 104 | if (newClassData != null) { 105 | loadedClasses.add(name); 106 | return loadClass(newClassData, name); 107 | } else { 108 | unavaiClasses.add(name); 109 | return parent.loadClass(name); 110 | } 111 | ~~~ 112 | 113 | Few more things about this Dynamic Class Loader: 114 | - The loaded classes have the same performance and other attributes as other classes loaded by the default class loader. 115 | - The Dynamic Class Loader can be Garbage Collected together with all of it's loaded classes and objects. 116 | 117 | In this example 1, the Dynamic ClassLoader is created with only 1 class path: "target/classes" ( in our current directory ), so it's capable of loading all the classes reside in that classpath. For all the classes not in there, it will have to refer to the parent class loader. For example we need to load String class in our StaticInt class, and our class loader does not have access to the rt.jar in our jre folder, so the String class of the parent class loader will be used. 118 | 119 | With the ability to load and use 2 versions of the same class, we are now thinking of dumping the old version and load the new one to replace it. In the next example, we will do that... continuously. 120 | 121 | 122 | Example 2: ReloadingContinuously 123 | ================ 124 | 125 | Source code: `src/main/java/qj/blog/classreloading/example2/ReloadingContinuously.java` 126 | 127 | This example will show you that the JRE can load and reload classes forever, with old classes dumped and garbage collected, and brand new class loaded from hard drive and put to use. 128 | 129 | ~~~ java 130 | 131 | Class userClass = new DynamicClassLoader("target/classes") 132 | .load("qj.blog.classreloading.example2.ReloadingContinuously$User"); 133 | ReflectUtil.invokeStatic("hobby", userClass); 134 | ThreadUtil.sleep(2000); 135 | 136 | ~~~ 137 | 138 | With every 2 seconds, the old `User` class will be dumped, a new one will be loaded and method `hobby` is invoked. 139 | 140 | Here is the `User` class definition: 141 | 142 | ~~~ java 143 | @SuppressWarnings("UnusedDeclaration") 144 | public static class User { 145 | public static void hobby() { 146 | playFootball(); // Will comment during runtime 147 | // playBasketball(); // Will uncomment during runtime 148 | } 149 | 150 | // Will comment during runtime 151 | public static void playFootball() { 152 | System.out.println("Play Football"); 153 | } 154 | 155 | // Will uncomment during runtime 156 | // public static void playBasketball() { 157 | // System.out.println("Play Basketball"); 158 | // } 159 | } 160 | ~~~ 161 | 162 | When running this application, you should try to comment and uncomment the code in User class, you will see that the newest definition will always be used. 163 | 164 | Here is the example output: 165 | 166 | ~~~ 167 | ... 168 | Play Football 169 | Play Football 170 | Play Football 171 | Play Basketball 172 | Play Basketball 173 | Play Basketball 174 | 175 | ~~~ 176 | 177 | Every time a new instance of `DynamicClassLoader` is created, and it will load the `User` class in the "`target/classes`" folder which we had set Eclipse or IntelliJ to output the latest class file. All old `DynamicClassLoader`s and old `User` classes will be unlinked and subjected to be garbage-collected. 178 | 179 | << Image `2_many_loaders.jpg` >> 180 | 181 | If you are familiar with JVM HotSpot, then it's noteworthy here that the class structure can also be changed and reloaded: playFootball method is to be removed and playBasketball method is to be added. This is different to HotSpot, which allow only method content be changed, or the class can not be reloaded. 182 | 183 | Now that we are capable of reloading a class, it is time to try reloading many classes at once. Let's try it out in the next example. 184 | 185 | 186 | Example 3: ContextReloading 187 | ================ 188 | 189 | Source code: `src/main/java/qj/blog/classreloading/example3/ContextReloading.java` 190 | 191 | The output of this example will be the same with example 2, the only difference is its more application-like structure with context, service and model objects... 192 | 193 | This example's source code is rather large, so I only put here parts of it, for the actual source code file, please refer to the GitHub project. 194 | 195 | The `main` method, which create the reloading cycles: 196 | 197 | ~~~ java 198 | for (;;) { 199 | Object context = createContext(); 200 | invokeHobbyService(context); 201 | ThreadUtil.sleep(2000); 202 | } 203 | ~~~ 204 | 205 | Method `createContext`, which create new `DynamicClassLoader` and create the context object from it: 206 | 207 | ~~~ java 208 | Class contextClass = new DynamicClassLoader("target/classes") 209 | .load("qj.blog.classreloading.example3.ContextReloading$Context"); 210 | Object context = newInstance(contextClass); 211 | invoke("init", context); 212 | return context; 213 | 214 | ~~~ 215 | 216 | Method `invokeHobbyService`, which put the context object to use: 217 | 218 | ~~~ java 219 | Object hobbyService = getFieldValue("hobbyService", context); 220 | invoke("hobby", hobbyService); 221 | ~~~ 222 | 223 | And here is the `Context` class, which describe how it links to the service: 224 | 225 | ~~~ java 226 | public static class Context { 227 | public HobbyService hobbyService = new HobbyService(); 228 | 229 | public void init() { 230 | // Init your services here 231 | hobbyService.user = new User(); 232 | } 233 | } 234 | ~~~ 235 | 236 | Please note that in this `Context` class, the code no longer requires to use Reflection, as everything is now loaded in the same `DynamicClassLoader`, they linked together, live and die together, so they can freely use direct link. 237 | 238 | The `Context` class in this example is much more complicated than the `User` class in the previous examples: it has link to other classes, it has the `init` method to be called every it is instantiated. Basically, it's very similar to real world application's context classes, (which keep hold of the application's modules and do dependency injection). So being able to reload this `Context` class together with all it's linked classes is a great step toward applying this technique to real life. 239 | 240 | << image `3_context_reloading.jpg` >> 241 | 242 | As the number of classes and objects grow, our step of "drop old versions" will also be more complicated, this is also the biggest reason why class reloading is so difficult. To possibly drop old versions we will have to make sure that once the new context created all the references to the old classes and objects are dropped. Soon I will show you the way to deal with it elegantly: 243 | 244 | The `main` method here will have a hold of the context object, and **that is the only link** to all the things that need to be dropped. If we break that link, the context object and the context class, and the service object ... will all be subjected to the `Garbage Collector`. 245 | 246 | A little explanation about why normally classes are so persistent and not get Garbage Collected: 247 | 248 | - Normally we load all our classes into the default classloader 249 | - The class - class loader is a 2-ways relationship with the classloader also cache all the classes it has loaded 250 | - So as long as the default classloader still connected to any alive thread, everything (all loaded classes) will be immune to the Garbage Collector. 251 | - That said, unless we can separate the code we want to reload with the one already loaded by the default class loader, our new code changes will never be applied during runtime. 252 | 253 | With this example, we see that reloading all application's classes is pretty easy, which is merely about keeping a thin, dropable connection from the live thread to the dynamic class loader in use. But what if we want some objects (and their classes) not to be reloaded and be reused between reloading cycles? Let's look at the next example. 254 | 255 | 256 | Example 4: KeepConnectionPool 257 | ================ 258 | 259 | Source code: `src/main/java/qj/blog/classreloading/example4/KeepConnectionPool.java` 260 | 261 | The `main` method: 262 | 263 | ~~~ java 264 | 265 | ConnectionPool pool = new ConnectionPool(); 266 | 267 | for (;;) { 268 | Object context = createContext(pool); 269 | 270 | invokeService(context); 271 | 272 | ThreadUtil.sleep(2000); 273 | } 274 | 275 | ~~~ 276 | 277 | So you can see that the trick here is loading the `ConnectionPool` class and instantiate it outside the reloading cycle, keeping it in the persisted space, and pass the reference to the `Context` objects 278 | 279 | The `createContext` method is also a little bit different: 280 | 281 | ~~~ java 282 | 283 | ExceptingClassLoader classLoader = new ExceptingClassLoader( 284 | (className) -> className.contains("$Connection"), 285 | "target/classes"); 286 | Class contextClass = classLoader.load(KeepConnectionPool.class.getName() + "$Context"); 287 | Object context = newInstance(contextClass); 288 | 289 | setFieldValue(pool, "pool", context); 290 | invoke("init", context); 291 | 292 | return context; 293 | 294 | ~~~ 295 | 296 | From now on, we will call the objects and classes that are reloaded with every cycle the "Reloadable space" and others the "Persisted space", which contain the objects and classes not recycled and not renewed during the reloading cycles. We will have to be very clear about which object or class stay in which space, thus drawing a separation line between these 2 spaces. 297 | 298 | << Image `4_persisting_connection.jpg` >> 299 | 300 | As seen from the picture, not only the Context Object and the UserService Object is referring to the ConnectionPool Object, but the Context and UserService classes are also referring to the ConnectionPool class. This is a very dangerous situation which often lead to confusion and failure. The ConnectionPool class must not be loaded by our `DynamicClassLoader`, there must be only one `ConnectionPool` class in the memory, which is the one loaded by the default `ClassLoader`. 301 | 302 | What if our `DynamicClassLoader` accidentally load the `ConnectionPool` class? Then the `ConnectionPool` object from the persisted space can not be passed to the Context object, because the Context object is expecting an object of a different class, which named `ConnectionPool` too, but is actually a different class. 303 | 304 | So how do we prevent our `DynamicClassLoader` from loading the `ConnectionPool` class? Instead of using `DynamicClassLoader`, this example use a subclass of it named: `ExceptingClassLoader`, which will pass the loading to super classloader base on a condition function: 305 | 306 | ~~~ java 307 | (className) -> className.contains("$Connection") 308 | ~~~ 309 | 310 | If we don't use `ExceptingClassLoader` here, then the `DynamicClassLoader` would load the `ConnectionPool` class because that class reside in the "`target/classes`" folder. Another way to prevent the `ConnectionPool` class to be picked up by our `DynamicClassLoader` is to compile the `ConnectionPool` class to a different folder, maybe put to a different module and it will be compiled separately. 311 | 312 | #### Rules for choosing space 313 | 314 | Now, the job gets really confusing, how to determine which classes would be in Persisted space, and which classes in Reloadable space. Here are the rules: 315 | 316 | - 1 class can serve in either or both Persisted space and Reloadable space if objects of its type are not sent across the 2 spaces. For example: utility classes with all static methods like `StringUtils` 317 | - 1 class has objects sent across the 2 spaces must be in Persisted space - like the `ConnectionPool` class. All linked classes must also be in Persisted space - like the `Connection` class which is linked from `ConnectionPool`. 318 | 319 | So you can see that the rules are not very restricted, except for the crossing classes that has objects transfer across the 2 spaces, all other classes can be freely used in either Persisted space or Reloadable space or both. Of course only classes in Reloadable space will enjoy being reloaded with reloading cycles. 320 | 321 | So the most challenging problem with Class Reloading is dealt with. In the next example, we will try to apply this technique to a simple Web Application, and enjoy the reloading Java classes just like any scripting language 322 | 323 | Example 5: Little Phone Book 324 | ================ 325 | 326 | Source code: `src/main/java/qj/blog/classreloading/example5/LittlePhoneBookMain.java` 327 | 328 | This example will be very similar to what a normal web application should look like. It is a Single Page Application with AngularJS, db is SQLite, Maven, and Jetty Embedded Web Server. 329 | 330 | Here is the reloadable space in the web server's structure: 331 | 332 | << Image `5_web_app.jpg` >> 333 | 334 | The web server will not hold reference to the real servlets (which must stay in the reloadable space, so to be reloaded ), what it holds is stub servlets, which with every call to its service method, will resolve the actual servlet in the actual context to run. 335 | 336 | This example also introduce a new object `ReloadingWebContext`, which provide to the Web Server all values like a normal Context, but internally hold reference to a actual context object that can be reloaded by a `DynamicClassLoader`. It is this `ReloadingWebContext` which provide stub servlets to the web server. 337 | 338 | << Image `5_reloading_web_context.jpg` >> 339 | 340 | The `ReloadingWebContext` will be the wrapper of the actual context, which will: 341 | 342 | - Reload the actual context when a HTTP GET to "/" in called. 343 | - Provide Stub servlet to web server. 344 | - Set values, invoke methods every time the actual context is initialized or destroyed. 345 | - Can be configured to reload context or not, and which ClassLoader is used for reloading. This will help when running the application in Production. 346 | 347 | Because it's very important to understanding how we isolate the Persisted space and Reloadable space, here are the 2 classes that are crossing between the 2 spaces: 348 | 349 | - Class `qj.util.funct.F0` for object `public F0 connF` in `Context`: Function object, will return a Connection each time the function is invoked. This class resides in the qj.util package, which is excluded from the `DynamicClassLoader`. 350 | - Class `java.sql.Connection` for object `public F0 connF` in `Context`: Normal sql connection object. This class does not reside in our `DynamicClassLoader`'s class path so it won't be picked up. 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | --------------------------------------------------------------------------------